···6464 ~/.cache/ms-playwright
6565 key: ${{ runner.os }}-playwright-${{ hashFiles('**/go.sum') }}
66666767+ - name: install playwright browsers
6868+ run: |
6969+ npx --yes playwright@1.50.1 install --with-deps
7070+ npx --yes playwright@1.50.1 run-server --port 3000 &
7171+6772 - name: Build
6873 run: go build ./...
69747075 - name: Test
7171- run: go test ./...
7676+ run: go test -v ./...
+8-4
cmd/anubis/main.go
···3434 bind = flag.String("bind", ":8923", "network address to bind HTTP to")
3535 bindNetwork = flag.String("bind-network", "tcp", "network family to bind HTTP to, e.g. unix, tcp")
3636 challengeDifficulty = flag.Int("difficulty", anubis.DefaultDifficulty, "difficulty of the challenge")
3737+ cookieDomain = flag.String("cookie-domain", "", "if set, the top-level domain that the Anubis cookie will be valid for")
3838+ cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support")
3739 ed25519PrivateKeyHex = flag.String("ed25519-private-key-hex", "", "private key used to sign JWTs, if not set a random one will be assigned")
3840 metricsBind = flag.String("metrics-bind", ":9090", "network address to bind metrics to")
3941 metricsBindNetwork = flag.String("metrics-bind-network", "tcp", "network family for the metrics server to bind to")
···189191 }
190192191193 s, err := libanubis.New(libanubis.Options{
192192- Next: rp,
193193- Policy: policy,
194194- ServeRobotsTXT: *robotsTxt,
195195- PrivateKey: priv,
194194+ Next: rp,
195195+ Policy: policy,
196196+ ServeRobotsTXT: *robotsTxt,
197197+ PrivateKey: priv,
198198+ CookieDomain: *cookieDomain,
199199+ CookiePartitioned: *cookiePartitioned,
196200 })
197201 if err != nil {
198202 log.Fatalf("can't construct libanubis.Server: %v", err)
+3
docs/docs/CHANGELOG.md
···1919- Fix default difficulty setting that was broken in a refactor
2020- Linting fixes
2121- Make dark mode diff lines readable in the documentation
2222+- Add the ability to set the cookie domain with the envvar `COOKIE_DOMAIN=techaro.lol` for all domains under `techaro.lol`
2323+- Add the ability to set the cookie partitioned flag with the envvar `COOKIE_PARTITIONED=true`
2424+- Fix CI based browser smoke test
22252326## v1.14.2
2427
+2
docs/docs/admin/installation.mdx
···4545| :------------------------ | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
4646| `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` |
4747| `BIND_NETWORK` | `tcp` | The address family that Anubis listens on. Accepts `tcp`, `unix` and anything Go's [`net.Listen`](https://pkg.go.dev/net#Listen) supports. |
4848+| `COOKIE_DOMAIN` | unset | The domain the Anubis challenge pass cookie should be set to. This should be set to the domain you bought from your registrar (EG: `techaro.lol` if your webapp is running on `anubis.techaro.lol`). See [here](https://stackoverflow.com/a/1063760) for more information. |
4949+| `COOKIE_PARTITIONED` | `false` | If set to `true`, enables the [partitioned (CHIPS) flag](https://developers.google.com/privacy-sandbox/cookies/chips), meaning that Anubis inside an iframe has a different set of cookies than the domain hosting the iframe. |
4850| `DIFFICULTY` | `5` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. |
4951| `ED25519_PRIVATE_KEY_HEX` | | The hex-encoded ed25519 private key used to sign Anubis responses. If this is not set, Anubis will generate one for you. This should be exactly 64 characters long. See below for details. |
5052| `METRICS_BIND` | `:9090` | The network address that Anubis serves Prometheus metrics on. See `BIND` for more information. |
+22-18
internal/test/playwright_test.go
···166166}
167167168168func TestPlaywrightBrowser(t *testing.T) {
169169- if os.Getenv("CI") == "true" {
170170- t.Skip("XXX(Xe): This is broken in CI, will fix later")
171171- }
172172-173169 if os.Getenv("DONT_USE_NETWORK") != "" {
174170 t.Skip("test requires network egress")
175171 return
···225221 t.Skip("skipping hard challenge with deadline")
226222 }
227223228228- perfomedAction := executeTestCase(t, tc, typ, anubisURL)
229229-224224+ var perfomedAction action
225225+ var err error
226226+ for i := 0; i < 5; i++ {
227227+ perfomedAction, err = executeTestCase(t, tc, typ, anubisURL)
228228+ if perfomedAction == tc.action {
229229+ break
230230+ }
231231+ time.Sleep(time.Duration(i+1) * 250 * time.Millisecond)
232232+ }
230233 if perfomedAction != tc.action {
231234 t.Errorf("unexpected test result, expected %s, got %s", tc.action, perfomedAction)
232232- } else {
233233- t.Logf("test passed")
235235+ }
236236+ if err != nil {
237237+ t.Fatalf("test error: %v", err)
234238 }
235239 })
236240 }
···247251 return u.String()
248252}
249253250250-func executeTestCase(t *testing.T, tc testCase, typ playwright.BrowserType, anubisURL string) action {
254254+func executeTestCase(t *testing.T, tc testCase, typ playwright.BrowserType, anubisURL string) (action, error) {
251255 deadline, _ := t.Deadline()
252256253257 browser, err := typ.Connect(buildBrowserConnect(typ.Name()), playwright.BrowserTypeConnectOptions{
254258 ExposeNetwork: playwright.String("<loopback>"),
255259 })
256260 if err != nil {
257257- t.Fatalf("could not connect to remote browser: %v", err)
261261+ return "", fmt.Errorf("could not connect to remote browser: %w", err)
258262 }
259263 defer browser.Close()
260264···266270 UserAgent: playwright.String(tc.userAgent),
267271 })
268272 if err != nil {
269269- t.Fatalf("could not create context: %v", err)
273273+ return "", fmt.Errorf("could not create context: %w", err)
270274 }
271275 defer ctx.Close()
272276273277 page, err := ctx.NewPage()
274278 if err != nil {
275275- t.Fatalf("could not create page: %v", err)
279279+ return "", fmt.Errorf("could not create page: %w", err)
276280 }
277281 defer page.Close()
278282···283287 Timeout: pwTimeout(tc, deadline),
284288 })
285289 if err != nil {
286286- pwFail(t, page, "could not navigate to test server: %v", err)
290290+ return "", pwFail(t, page, "could not navigate to test server: %v", err)
287291 }
288292289293 hadChallenge := false
···294298 hadChallenge = true
295299 case actionDeny:
296300 checkImage(t, tc, deadline, page, "#image[src*=sad]")
297297- return actionDeny
301301+ return actionDeny, nil
298302 }
299303300304 // Ensure protected resource was provided.
···317321 }
318322319323 if hadChallenge {
320320- return actionChallenge
324324+ return actionChallenge, nil
321325 } else {
322322- return actionAllow
326326+ return actionAllow, nil
323327 }
324328}
325329···342346 }
343347}
344348345345-func pwFail(t *testing.T, page playwright.Page, format string, args ...any) {
349349+func pwFail(t *testing.T, page playwright.Page, format string, args ...any) error {
346350 t.Helper()
347351348352 saveScreenshot(t, page)
349349- t.Fatalf(format, args...)
353353+ return fmt.Errorf(format, args...)
350354}
351355352356func pwTimeout(tc testCase, deadline time.Time) *float64 {