internal/httpd/checkorigin_test.go

3f4afd2210627e46fd0cea1f5831857af71638f6
gitbay/internal/httpd/checkorigin_test.go history · blame · raw

62 lines · 2404 bytes

 1package httpd
 2
 3import (
 4	"net/http"
 5	"net/http/httptest"
 6	"testing"
 7
 8	"gitbay.org/gitbay/internal/config"
 9)
10
11// TestMutatingRoutesRequireCheckOrigin pins the invariant checkOrigin's doc
12// comment rests on (#155, #157): the session cookie is SameSite=Lax, which
13// withholds it from a cross-site POST but not a cross-site top-level GET, so
14// checkOrigin has to be the thing that refuses every other mutating route.
15// checkOrigin is the outermost wrapper, so a cross-site Origin is refused
16// with 403 before any handler logic runs — no session or repository needed.
17//
18// Two routes are exempt on purpose, not by omission:
19var checkOriginAllowlist = map[string]string{
20	// Authenticates only via "Authorization: Bearer ...". A cross-site
21	// browser request cannot attach one, so there is no cookie for
22	// checkOrigin to protect here.
23	"POST /api/v1/cmd": "bearer-token auth, no cookie in play",
24	// Consumes a single-use token from the query string and sets no
25	// cookie on failure; browsers withhold Origin from a cross-site
26	// top-level GET, which is the only way this route is ever reached
27	// cross-site.
28	"GET /login": "single-use token GET, no cookie read",
29}
30
31func TestMutatingRoutesRequireCheckOrigin(t *testing.T) {
32	cfg := config.Default()
33	cfg.Web.Mode = "accounts" // superset of accounts-mode routes
34	cfg.API.Enabled = true
35	// /register is gated on registration.mode != "closed" (routes.go), which
36	// config.Default() leaves at "closed" — the production instance runs
37	// "open", and that is the one deployed value the route table hides its
38	// routes behind if this test's cfg does not open it too. "open" and
39	// "invite" gate the route identically (both are just != "closed"), so
40	// there is no second code path in routes.go for looping over both to
41	// reach; "open" alone matches production and is enough.
42	cfg.Registration.Mode = "open"
43	s := New(cfg, nil)
44
45	for _, r := range s.Routes() {
46		if !r.Mutating {
47			continue
48		}
49		key := r.Method + " " + r.Pattern
50		if _, exempt := checkOriginAllowlist[key]; exempt {
51			continue
52		}
53		req := httptest.NewRequest(r.Method, "http://example.com/", nil)
54		req.Header.Set("Origin", "https://evil.example")
55		rr := httptest.NewRecorder()
56		r.Handler(rr, req)
57		if rr.Code != http.StatusForbidden {
58			t.Errorf("%s: cross-site Origin got status %d, want %d (missing checkOrigin?)",
59				key, rr.Code, http.StatusForbidden)
60		}
61	}
62}