aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/web_test.go
diff options
context:
space:
mode:
authorgrm <grm@eyesin.space>2026-09-18 13:47:49 +0300
committergrm <grm@eyesin.space>2026-09-18 13:47:49 +0300
commit3eeaa6d9a33f9294ade64c8ff26144fba86a379e (patch)
tree1c4bf44884ea59f7e3d9ca12008846f08bf72d16 /internal/web/web_test.go
parent254733b0566830a46e5a44c2d3127f57bfaceb65 (diff)
downloadblogspace-3eeaa6d9a33f9294ade64c8ff26144fba86a379e.tar.gz
blogspace-3eeaa6d9a33f9294ade64c8ff26144fba86a379e.tar.bz2
blogspace-3eeaa6d9a33f9294ade64c8ff26144fba86a379e.zip
Security: Add HTTPS and TRUST_PROXY settings
The app only ever speaks plain HTTP, so it could not know the site was served over TLS: generated links were http:// (the /webadmin bounce from a blog host sent the login page over http), the session cookie was never Secure and nothing sent HSTS. HTTPS=true fixes all three; ClearSessionCookie now uses the same attributes as the set, since a browser only replaces a cookie whose Secure flag matches. TRUST_PROXY=true makes the client address the last X-Forwarded-For entry — the one our proxy appended — so throttling and the log see real addresses instead of the proxy's; earlier entries are whatever the client sent and are ignored. Production startup warns when HTTPS is off. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'internal/web/web_test.go')
-rw-r--r--internal/web/web_test.go28
1 files changed, 28 insertions, 0 deletions
diff --git a/internal/web/web_test.go b/internal/web/web_test.go
index 49915f4..42d3b3a 100644
--- a/internal/web/web_test.go
+++ b/internal/web/web_test.go
@@ -975,3 +975,31 @@ func TestLoginBodyCapped(t *testing.T) {
t.Errorf("got %d, want 400", rec.Code)
}
}
+
+// With HTTPS on, links are https, the cookie is Secure and HSTS is sent; with TRUST_PROXY the client is the last X-Forwarded-For hop.
+func TestHTTPSAndTrustProxy(t *testing.T) {
+ cfg := &config.Config{BaseDomain: "example.com", JWTSecret: []byte("x"), HTTPS: true, TrustProxy: true}
+ if cfg.RootURL() != "https://example.com" || cfg.BlogURL("a") != "https://a.example.com" {
+ t.Errorf("urls: %s %s", cfg.RootURL(), cfg.BlogURL("a"))
+ }
+ s := NewServer(cfg, nil)
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest("GET", "/webadmin", nil)
+ req.Host = "example.com"
+ req.AddCookie(&http.Cookie{Name: "session", Value: "garbage"}) // a bad cookie is cleared, with Secure
+ req.Header.Set("X-Forwarded-For", "203.0.113.9, 10.0.0.2")
+ s.ServeHTTP(rec, req)
+ if h := rec.Header().Get("Strict-Transport-Security"); !strings.Contains(h, "includeSubDomains") {
+ t.Errorf("HSTS = %q", h)
+ }
+ if c := rec.Header().Get("Set-Cookie"); !strings.Contains(c, "Secure") || !strings.Contains(c, "Max-Age=0") {
+ t.Errorf("cleared cookie = %q", c)
+ }
+ if ip := s.clientIP(req); ip != "10.0.0.2" {
+ t.Errorf("clientIP = %q, want the last hop", ip)
+ }
+ cfg.TrustProxy = false
+ if ip := s.clientIP(req); ip != "192.0.2.1" {
+ t.Errorf("clientIP without TRUST_PROXY = %q, want the peer", ip)
+ }
+}