package auth import ( "net/http" "net/http/httptest" "testing" "time" "github.com/google/uuid" ) const testSecret = "test-geheimnis-mit-mehr-als-32-zeichen!!" // Anmelden und die gesetzten Cookies zurückgeben. func signIn(t *testing.T, ss *SessionStore) []*http.Cookie { t.Helper() w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodGet, "/", nil) if err := ss.SetUser(w, r, uuid.New()); err != nil { t.Fatalf("SetUser: %v", err) } return w.Result().Cookies() } // Eine **frische** Anfrage mit diesen Cookies. // // Jedes Mal eine neue, und das ist keine Umständlichkeit: gorilla legt die // entschlüsselte Sitzung im Zusammenhang der Anfrage ab und gibt sie beim // zweiten Zugriff unverändert zurück. Zweimal dieselbe Anfrage zu befragen // prüfte also den Zwischenspeicher und nicht die Frist – der erste Entwurf // dieses Tests ist genau darauf hereingefallen. func withCookies(cookies []*http.Cookie) *http.Request { r := httptest.NewRequest(http.MethodGet, "/", nil) for _, c := range cookies { r.AddCookie(c) } return r } func signedIn(t *testing.T, ss *SessionStore) *http.Request { t.Helper() return withCookies(signIn(t, ss)) } // Die Frist muss auch für den **Inhalt** gelten, nicht nur für das Cookie. // // Der Browser wirft ein abgelaufenes Cookie von selbst weg – wer es vorher // herauskopiert hat, aber nicht. Ohne store.MaxAge() blieb die Signatur 30 // Tage gültig, egal was in SESSION_MAX_AGE stand. func TestSessionExpiresAfterMaxAge(t *testing.T) { ss := NewSessionStore(testSecret, 1) // eine Sekunde cookies := signIn(t, ss) if _, ok := ss.GetUserID(withCookies(cookies)); !ok { t.Fatal("frische Sitzung wurde nicht erkannt") } time.Sleep(2100 * time.Millisecond) if _, ok := ss.GetUserID(withCookies(cookies)); ok { t.Error("abgelaufene Sitzung gilt weiter – die Frist wirkt nur im Browser") } } // Nicht bei jedem Aufruf ein neues Cookie: Sonst trüge jede Antwort ein // Set-Cookie, und beim Laden einer Dateiliste sind das dreißig Stück. func TestTouchIsThrottled(t *testing.T) { ss := NewSessionStore(testSecret, 3600) r := signedIn(t, ss) w := httptest.NewRecorder() ss.Touch(w, r) if len(w.Result().Cookies()) != 0 { t.Error("Sitzung wurde sofort erneuert, obwohl sie eben erst entstand") } } // Nach Ablauf der Bremse verlängert sie sich – das ist der eigentliche Zweck. func TestTouchExtendsSession(t *testing.T) { old := refreshInterval refreshInterval = 0 defer func() { refreshInterval = old }() ss := NewSessionStore(testSecret, 3600) r := signedIn(t, ss) w := httptest.NewRecorder() ss.Touch(w, r) cookies := w.Result().Cookies() if len(cookies) == 0 { t.Fatal("Sitzung wurde nicht verlängert") } // Das erneuerte Cookie muss dieselbe Anmeldung tragen. next := httptest.NewRequest(http.MethodGet, "/", nil) for _, c := range cookies { next.AddCookie(c) } if _, ok := ss.GetUserID(next); !ok { t.Error("nach dem Verlängern ist niemand mehr angemeldet") } } // Eine Anfrage ohne Cookie darf keine Sitzung erfinden. func TestTouchIgnoresAnonymous(t *testing.T) { ss := NewSessionStore(testSecret, 3600) w := httptest.NewRecorder() ss.Touch(w, httptest.NewRequest(http.MethodGet, "/", nil)) if len(w.Result().Cookies()) != 0 { t.Error("für einen Unangemeldeten wurde ein Cookie gesetzt") } } // Abmelden muss stärker sein als Verlängern. func TestClearEndsSession(t *testing.T) { ss := NewSessionStore(testSecret, 3600) r := signedIn(t, ss) w := httptest.NewRecorder() if err := ss.Clear(w, r); err != nil { t.Fatalf("Clear: %v", err) } for _, c := range w.Result().Cookies() { if c.Name == sessionName && c.MaxAge >= 0 { t.Errorf("Abmelden löscht das Cookie nicht (MaxAge=%d)", c.MaxAge) } } }