package httpapi import ( "encoding/base64" "strings" "testing" ) func TestEncryptRefreshRoundTrip(t *testing.T) { s := &Server{sessionKey: deriveSessionKey("a-strong-session-secret")} plain := strings.Repeat("refresh.token.", 2000) // ~28 KB, mimics Casdoor's large JWT enc, err := s.encryptRefresh(plain) if err != nil { t.Fatalf("encrypt: %v", err) } if strings.Contains(enc, plain) { t.Fatal("ciphertext leaks plaintext") } got, err := s.decryptRefresh(enc) if err != nil { t.Fatalf("decrypt: %v", err) } if got != plain { t.Fatalf("roundtrip mismatch: len(got)=%d, len(want)=%d", len(got), len(plain)) } } func TestEncryptRefreshNonceIsRandom(t *testing.T) { s := &Server{sessionKey: deriveSessionKey("secret")} a, _ := s.encryptRefresh("same plaintext") b, _ := s.encryptRefresh("same plaintext") if a == b { t.Fatal("two encryptions of the same plaintext are identical — nonce not random") } } func TestDecryptRefreshRejectsTamper(t *testing.T) { s := &Server{sessionKey: deriveSessionKey("secret")} enc, err := s.encryptRefresh("token") if err != nil { t.Fatalf("encrypt: %v", err) } raw, err := base64.StdEncoding.DecodeString(enc) if err != nil { t.Fatalf("decode: %v", err) } raw[len(raw)-1] ^= 0xFF // flip a bit in the GCM tag if _, err := s.decryptRefresh(base64.StdEncoding.EncodeToString(raw)); err == nil { t.Fatal("tampered ciphertext must fail authentication") } } func TestDecryptRefreshRejectsWrongKey(t *testing.T) { enc, err := (&Server{sessionKey: deriveSessionKey("key-one")}).encryptRefresh("token") if err != nil { t.Fatalf("encrypt: %v", err) } if _, err := (&Server{sessionKey: deriveSessionKey("key-two")}).decryptRefresh(enc); err == nil { t.Fatal("decryption under a different key must fail") } } func TestEncryptRefreshNoKey(t *testing.T) { s := &Server{sessionKey: nil} if _, err := s.encryptRefresh("token"); err == nil { t.Fatal("encrypt without a key must error") } } func TestSessionIDDeterministicAndHashed(t *testing.T) { raw, id, err := newSessionToken() if err != nil { t.Fatalf("newSessionToken: %v", err) } if id != sessionID(raw) { t.Fatal("newSessionToken id must equal sessionID(raw)") } if id == raw || strings.Contains(id, raw) { t.Fatal("stored id must be a hash of the cookie value, not the value itself") } // SHA-256 hex is always 64 chars. if len(id) != 64 { t.Fatalf("session id length: got %d, want 64", len(id)) } } func TestNewSessionTokenUnique(t *testing.T) { seen := make(map[string]bool) for i := 0; i < 1000; i++ { raw, _, err := newSessionToken() if err != nil { t.Fatalf("newSessionToken: %v", err) } if seen[raw] { t.Fatal("duplicate session token generated") } seen[raw] = true } } func TestDeriveSessionKey(t *testing.T) { if deriveSessionKey("") != nil { t.Fatal("empty secret must yield a nil key (feature disabled)") } if got := len(deriveSessionKey("x")); got != 32 { t.Fatalf("derived key length: got %d, want 32 (AES-256)", got) } } func TestDeriveSiteOrigin(t *testing.T) { cases := []struct{ in, want string }{ {"https://drop.example.net/oauth/callback", "https://drop.example.net"}, {"http://localhost:5173/oauth/callback", "http://localhost:5173"}, {"", ""}, {"not a url", ""}, } for _, c := range cases { if got := deriveSiteOrigin(c.in); got != c.want { t.Errorf("deriveSiteOrigin(%q) = %q, want %q", c.in, got, c.want) } } }