13e8e73da7
The browser UI signs in with a Discord authorization code grant (identify + guilds.members.read) instead of a shared password. Guild membership is the gate; the owner's Discord ID is the only identity allowed in while registration is closed. Sessions become rows in a sessions table with opaque random ids — the cookie carries only the id, looked up and expiry-checked per request — so deleting a row revokes a session. HMAC cookie signing, its derived key, and WEB_PASSWORD are gone, and no replacement signing secret is introduced (ADR-0002). Discord's API base is configurable (DISCORD_API_BASE); the full flow is tested through the real router against a local stub, including the form-encoded token exchange Discord rejects if sent as JSON.
85 lines
2.4 KiB
Go
85 lines
2.4 KiB
Go
package store
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestCreateAndGetSession(t *testing.T) {
|
|
s := newTestStore(t)
|
|
owner := s.OwnerID()
|
|
|
|
sess, err := s.CreateSession("sess-1", owner, time.Hour)
|
|
if err != nil {
|
|
t.Fatalf("CreateSession: %v", err)
|
|
}
|
|
if sess.ID != "sess-1" || sess.ReaderID != owner {
|
|
t.Fatalf("CreateSession returned %+v, want id sess-1 reader %d", sess, owner)
|
|
}
|
|
|
|
got, ok, err := s.GetSession("sess-1", time.Now())
|
|
if err != nil || !ok {
|
|
t.Fatalf("GetSession: ok=%v err=%v, want ok", ok, err)
|
|
}
|
|
if got.ReaderID != owner {
|
|
t.Fatalf("session reader = %d, want %d", got.ReaderID, owner)
|
|
}
|
|
}
|
|
|
|
func TestGetSessionUnknownID(t *testing.T) {
|
|
s := newTestStore(t)
|
|
if _, ok, err := s.GetSession("nope", time.Now()); err != nil || ok {
|
|
t.Fatalf("GetSession(unknown) = ok=%v err=%v, want ok=false", ok, err)
|
|
}
|
|
}
|
|
|
|
func TestExpiredSessionIsGone(t *testing.T) {
|
|
s := newTestStore(t)
|
|
owner := s.OwnerID()
|
|
if _, err := s.CreateSession("sess-exp", owner, -time.Minute); err != nil {
|
|
t.Fatalf("CreateSession: %v", err)
|
|
}
|
|
|
|
now := time.Now()
|
|
if _, ok, err := s.GetSession("sess-exp", now); err != nil || ok {
|
|
t.Fatalf("GetSession(expired) = ok=%v err=%v, want ok=false", ok, err)
|
|
}
|
|
// The expired row is deleted on lookup, so the next call cannot revive it.
|
|
if _, ok, err := s.GetSession("sess-exp", now.Add(-time.Hour)); err != nil || ok {
|
|
t.Fatalf("GetSession(expired again) = ok=%v err=%v, want ok=false", ok, err)
|
|
}
|
|
}
|
|
|
|
func TestDeleteSessionRevokes(t *testing.T) {
|
|
s := newTestStore(t)
|
|
owner := s.OwnerID()
|
|
if _, err := s.CreateSession("sess-del", owner, time.Hour); err != nil {
|
|
t.Fatalf("CreateSession: %v", err)
|
|
}
|
|
if err := s.DeleteSession("sess-del"); err != nil {
|
|
t.Fatalf("DeleteSession: %v", err)
|
|
}
|
|
if _, ok, err := s.GetSession("sess-del", time.Now()); err != nil || ok {
|
|
t.Fatalf("GetSession after delete = ok=%v err=%v, want ok=false", ok, err)
|
|
}
|
|
// Deleting twice is not an error.
|
|
if err := s.DeleteSession("sess-del"); err != nil {
|
|
t.Fatalf("DeleteSession twice: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDeleteSessionIsPerReader(t *testing.T) {
|
|
s := newTestStore(t)
|
|
other := secondReader(t, s)
|
|
if _, err := s.CreateSession("sess-other", other, time.Hour); err != nil {
|
|
t.Fatalf("CreateSession: %v", err)
|
|
}
|
|
got, ok, err := s.GetSession("sess-other", time.Now())
|
|
if err != nil || !ok {
|
|
t.Fatalf("GetSession: ok=%v err=%v, want ok", ok, err)
|
|
}
|
|
if got.ReaderID != other {
|
|
t.Fatalf("session reader = %d, want %d", got.ReaderID, other)
|
|
}
|
|
}
|