summaryrefslogtreecommitdiffstats
path: root/internal/submit/types_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/submit/types_test.go')
-rw-r--r--internal/submit/types_test.go125
1 files changed, 124 insertions, 1 deletions
diff --git a/internal/submit/types_test.go b/internal/submit/types_test.go
index a2a7c1a..16a2b84 100644
--- a/internal/submit/types_test.go
+++ b/internal/submit/types_test.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"net/http"
"net/http/httptest"
+ "net/url"
"strings"
"testing"
"testing/fstest"
@@ -15,13 +16,135 @@ import (
func newTestApp(t *testing.T) *App {
t.Helper()
- app, err := NewApp(config.Config{}, nil, storage.NewReplayCache(time.Minute), fstest.MapFS{}, "<!doctype html>")
+ app, err := NewApp(config.Config{}, nil, nil, storage.NewReplayCache(time.Minute), fstest.MapFS{}, "<!doctype html>")
if err != nil {
t.Fatalf("NewApp returned error: %v", err)
}
return app
}
+func TestCSRFCookieHasNoElapsedTimeLimit(t *testing.T) {
+ app := newTestApp(t)
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ rec := httptest.NewRecorder()
+
+ app.handleIndex(rec, req)
+
+ cookies := rec.Result().Cookies()
+ if len(cookies) != 1 {
+ t.Fatalf("unexpected cookie count: got %d want 1", len(cookies))
+ }
+ cookie := cookies[0]
+ if cookie.Name != "m2u_csrf" {
+ t.Fatalf("unexpected cookie name: %q", cookie.Name)
+ }
+ if cookie.MaxAge != 0 || !cookie.Expires.IsZero() {
+ t.Fatalf("CSRF cookie has an elapsed-time expiry: MaxAge=%d Expires=%v", cookie.MaxAge, cookie.Expires)
+ }
+ if !cookie.HttpOnly || cookie.SameSite != http.SameSiteStrictMode {
+ t.Fatal("CSRF cookie security attributes were weakened")
+ }
+}
+
+func TestCSRFRefreshIssuesValidToken(t *testing.T) {
+ app := newTestApp(t)
+ req := httptest.NewRequest(http.MethodGet, "/csrf", nil)
+ rec := httptest.NewRecorder()
+
+ app.handleCSRF(rec, req)
+
+ if got := rec.Header().Get("Cache-Control"); got != "no-store" {
+ t.Fatalf("unexpected Cache-Control: %q", got)
+ }
+ var body map[string]string
+ if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
+ t.Fatalf("decode CSRF response: %v", err)
+ }
+ token := body["token"]
+ if token == "" {
+ t.Fatal("CSRF refresh returned an empty token")
+ }
+ cookies := rec.Result().Cookies()
+ if len(cookies) != 1 {
+ t.Fatalf("unexpected cookie count: got %d want 1", len(cookies))
+ }
+
+ form := url.Values{"csrf_token": {token}}
+ post := httptest.NewRequest(http.MethodPost, "/submit", strings.NewReader(form.Encode()))
+ post.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ post.AddCookie(cookies[0])
+ if err := post.ParseForm(); err != nil {
+ t.Fatalf("parse form: %v", err)
+ }
+ if !app.verifyCSRF(post) {
+ t.Fatal("refreshed CSRF token did not verify")
+ }
+}
+
+func TestCSRFCookieSurvivesRestartWithPersistentKey(t *testing.T) {
+ cfg := config.Config{Security: config.SecurityConfig{CSRFKey: []byte(strings.Repeat("k", 32))}}
+ newApp := func() *App {
+ app, err := NewApp(cfg, nil, nil, storage.NewReplayCache(time.Minute), fstest.MapFS{}, "<!doctype html>")
+ if err != nil {
+ t.Fatalf("NewApp returned error: %v", err)
+ }
+ return app
+ }
+
+ first := httptest.NewRecorder()
+ newApp().handleCSRF(first, httptest.NewRequest(http.MethodGet, "/csrf", nil))
+ var firstBody map[string]string
+ if err := json.Unmarshal(first.Body.Bytes(), &firstBody); err != nil {
+ t.Fatalf("decode first CSRF response: %v", err)
+ }
+ cookies := first.Result().Cookies()
+ if len(cookies) != 1 {
+ t.Fatalf("unexpected cookie count: %d", len(cookies))
+ }
+
+ requestAfterRestart := httptest.NewRequest(http.MethodGet, "/csrf", nil)
+ requestAfterRestart.AddCookie(cookies[0])
+ second := httptest.NewRecorder()
+ newApp().handleCSRF(second, requestAfterRestart)
+ var secondBody map[string]string
+ if err := json.Unmarshal(second.Body.Bytes(), &secondBody); err != nil {
+ t.Fatalf("decode second CSRF response: %v", err)
+ }
+ if secondBody["token"] != firstBody["token"] {
+ t.Fatal("restart rotated a still-valid session token")
+ }
+ if got := len(second.Result().Cookies()); got != 0 {
+ t.Fatalf("valid session unexpectedly rotated %d cookies", got)
+ }
+}
+
+type transportStateStub struct {
+ ready bool
+}
+
+func (s *transportStateStub) Ready() bool { return s.ready }
+func (s *transportStateStub) MarkSuccess() { s.ready = true }
+func (s *transportStateStub) MarkFailure(error) { s.ready = false }
+
+func TestReadinessUsesCachedTransportState(t *testing.T) {
+ app := newTestApp(t)
+ state := &transportStateStub{}
+ app.transport = state
+
+ recorder := httptest.NewRecorder()
+ app.handleReadiness(recorder, httptest.NewRequest(http.MethodGet, "/readyz", nil))
+ if recorder.Code != http.StatusServiceUnavailable {
+ t.Fatalf("unready status = %d, want %d", recorder.Code, http.StatusServiceUnavailable)
+ }
+
+ state.ready = true
+ recorder = httptest.NewRecorder()
+ app.handleReadiness(recorder, httptest.NewRequest(http.MethodGet, "/readyz", nil))
+ if recorder.Code != http.StatusOK {
+ t.Fatalf("ready status = %d, want %d", recorder.Code, http.StatusOK)
+ }
+}
+
func TestSuccessResponseJSONNoStore(t *testing.T) {
app := newTestApp(t)
req := httptest.NewRequest(http.MethodPost, "/submit", nil)