Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68
130 lines
3.6 KiB
Go
130 lines
3.6 KiB
Go
package web
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"git.ravensburg.dev/cabele/opnsense-portal/internal/auth"
|
|
)
|
|
|
|
func testIdentity() *auth.Identity {
|
|
return &auth.Identity{Username: "mmueller", Groups: []string{"VPN-Users"}}
|
|
}
|
|
|
|
func TestCreateAndGetSession(t *testing.T) {
|
|
st := NewSessionStore(10*time.Minute, nil)
|
|
sess, err := st.Create(testIdentity())
|
|
if err != nil {
|
|
t.Fatalf("Create: %v", err)
|
|
}
|
|
if len(sess.Token) < 32 {
|
|
t.Errorf("Token %q ist zu kurz für ausreichende Entropie", sess.Token)
|
|
}
|
|
if sess.CSRF == "" || sess.CSRF == sess.Token {
|
|
t.Error("CSRF-Token muss existieren und vom Session-Token verschieden sein")
|
|
}
|
|
got, ok := st.Get(sess.Token)
|
|
if !ok || got.Identity.Username != "mmueller" {
|
|
t.Fatalf("Get = %+v, ok=%v", got, ok)
|
|
}
|
|
}
|
|
|
|
func TestTokensAreUnique(t *testing.T) {
|
|
st := NewSessionStore(time.Minute, nil)
|
|
seen := map[string]bool{}
|
|
for i := 0; i < 200; i++ {
|
|
s, err := st.Create(testIdentity())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if seen[s.Token] {
|
|
t.Fatalf("doppelter Token %q", s.Token)
|
|
}
|
|
seen[s.Token] = true
|
|
}
|
|
}
|
|
|
|
func TestSessionExpires(t *testing.T) {
|
|
now := time.Date(2026, 8, 14, 12, 0, 0, 0, time.UTC)
|
|
st := NewSessionStore(10*time.Minute, func() time.Time { return now })
|
|
|
|
sess, _ := st.Create(testIdentity())
|
|
now = now.Add(9 * time.Minute)
|
|
if _, ok := st.Get(sess.Token); !ok {
|
|
t.Fatal("Session darf nach 9 von 10 Minuten nicht abgelaufen sein")
|
|
}
|
|
now = now.Add(2 * time.Minute)
|
|
if _, ok := st.Get(sess.Token); ok {
|
|
t.Fatal("Session muss nach Ablauf der TTL ungültig sein")
|
|
}
|
|
if st.Count() != 0 {
|
|
t.Errorf("abgelaufene Session muss entfernt werden, Count = %d", st.Count())
|
|
}
|
|
}
|
|
|
|
func TestTTLIsAbsoluteNotSliding(t *testing.T) {
|
|
now := time.Date(2026, 8, 14, 12, 0, 0, 0, time.UTC)
|
|
st := NewSessionStore(10*time.Minute, func() time.Time { return now })
|
|
sess, _ := st.Create(testIdentity())
|
|
|
|
// Regelmäßige Zugriffe dürfen die Sitzung nicht verlängern.
|
|
for i := 0; i < 5; i++ {
|
|
now = now.Add(2 * time.Minute)
|
|
st.Get(sess.Token)
|
|
}
|
|
if _, ok := st.Get(sess.Token); ok {
|
|
t.Fatal("die TTL ist absolut; Aktivität darf sie nicht verlängern")
|
|
}
|
|
}
|
|
|
|
func TestDestroyRemovesSession(t *testing.T) {
|
|
st := NewSessionStore(time.Minute, nil)
|
|
sess, _ := st.Create(testIdentity())
|
|
st.Destroy(sess.Token)
|
|
if _, ok := st.Get(sess.Token); ok {
|
|
t.Fatal("zerstörte Session darf nicht mehr auffindbar sein")
|
|
}
|
|
st.Destroy("existiert-nicht") // darf nicht paniken
|
|
}
|
|
|
|
func TestGetRejectsUnknownAndEmptyToken(t *testing.T) {
|
|
st := NewSessionStore(time.Minute, nil)
|
|
if _, ok := st.Get(""); ok {
|
|
t.Error("leerer Token darf nie gültig sein")
|
|
}
|
|
if _, ok := st.Get("beliebig"); ok {
|
|
t.Error("unbekannter Token darf nie gültig sein")
|
|
}
|
|
}
|
|
|
|
func TestValidCSRF(t *testing.T) {
|
|
st := NewSessionStore(time.Minute, nil)
|
|
sess, _ := st.Create(testIdentity())
|
|
if !sess.ValidCSRF(sess.CSRF) {
|
|
t.Error("eigenes CSRF-Token muss akzeptiert werden")
|
|
}
|
|
for _, bad := range []string{"", "falsch", sess.CSRF + "x", sess.CSRF[:len(sess.CSRF)-1]} {
|
|
if sess.ValidCSRF(bad) {
|
|
t.Errorf("CSRF-Token %q darf nicht akzeptiert werden", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGCRemovesOnlyExpired(t *testing.T) {
|
|
now := time.Date(2026, 8, 14, 12, 0, 0, 0, time.UTC)
|
|
st := NewSessionStore(10*time.Minute, func() time.Time { return now })
|
|
old, _ := st.Create(testIdentity())
|
|
now = now.Add(11 * time.Minute)
|
|
fresh, _ := st.Create(testIdentity())
|
|
|
|
st.GC()
|
|
if st.Count() != 1 {
|
|
t.Fatalf("Count = %d, want 1", st.Count())
|
|
}
|
|
if _, ok := st.Get(old.Token); ok {
|
|
t.Error("alte Session muss weg sein")
|
|
}
|
|
if _, ok := st.Get(fresh.Token); !ok {
|
|
t.Error("frische Session muss bleiben")
|
|
}
|
|
}
|