Files
CosmicClash/server/domain/auth_test.go
T
2026-08-31 22:28:45 +01:00

149 lines
6.1 KiB
Go

package domain
import (
"errors"
"testing"
"time"
)
func TestTicketVerifierBindsAppIdentityExpiryAndSingleUse(t *testing.T) {
now := time.Unix(1000, 0)
verifier, _ := NewTicketVerifier(480)
ticket := SteamTicket{TicketID: "ticket-1", SteamID: "steam-1", AppID: 480, ExpiresAt: now.Add(time.Minute)}
resolve := func(steamID string) (string, bool) { return "player-1", steamID == "steam-1" }
identity, err := verifier.Verify(ticket, resolve, now)
if err != nil || identity.PlayerID != "player-1" || identity.SteamID != "steam-1" {
t.Fatalf("identity = %+v err=%v", identity, err)
}
if _, err := verifier.Verify(ticket, resolve, now); !errors.Is(err, ErrTicketRejected) {
t.Fatalf("ticket replay accepted: %v", err)
}
wrong := ticket
wrong.TicketID = "ticket-2"
wrong.AppID = 481
if _, err := verifier.Verify(wrong, resolve, now); !errors.Is(err, ErrTicketRejected) {
t.Fatalf("wrong app accepted: %v", err)
}
expired := ticket
expired.TicketID = "ticket-3"
expired.ExpiresAt = now
if _, err := verifier.Verify(expired, resolve, now); !errors.Is(err, ErrTicketRejected) {
t.Fatalf("expired ticket accepted: %v", err)
}
unknown := ticket
unknown.TicketID = "ticket-4"
unknown.SteamID = "steam-unknown"
if _, err := verifier.Verify(unknown, resolve, now); !errors.Is(err, ErrTicketRejected) {
t.Fatalf("unresolved SteamID accepted: %v", err)
}
}
func TestTicketVerifierRejectsBannedIdentityBeforeConsumption(t *testing.T) {
now := time.Unix(1000, 0)
verifier, _ := NewTicketVerifier(480)
if err := verifier.SetBanned("player-1", true); err != nil {
t.Fatal(err)
}
ticket := SteamTicket{TicketID: "ticket-1", SteamID: "steam-1", AppID: 480, ExpiresAt: now.Add(time.Minute)}
resolve := func(string) (string, bool) { return "player-1", true }
if _, err := verifier.Verify(ticket, resolve, now); !errors.Is(err, ErrTicketRejected) {
t.Fatalf("banned ticket accepted: %v", err)
}
if err := verifier.SetBanned("player-1", false); err != nil {
t.Fatal(err)
}
if _, err := verifier.Verify(ticket, resolve, now); err != nil {
t.Fatalf("unbanned ticket remained consumed: %v", err)
}
}
func TestSessionIsOpaqueShortLivedAndRevocable(t *testing.T) {
now := time.Unix(1000, 0)
store := NewSessionStore()
session, token, err := store.Issue("player-1", time.Minute, now)
if err != nil || token == "" || session.PlayerID != "player-1" {
t.Fatalf("issue = %+v token=%q err=%v", session, token, err)
}
if _, err := store.Authenticate(session.SessionID, "wrong", now); !errors.Is(err, ErrSessionRejected) {
t.Fatalf("wrong token accepted: %v", err)
}
if got, err := store.Authenticate(session.SessionID, token, now.Add(59*time.Second)); err != nil || got.SessionID != session.SessionID {
t.Fatalf("valid auth = %+v err=%v", got, err)
}
if err := store.Revoke(session.SessionID, now); err != nil {
t.Fatal(err)
}
if _, err := store.Authenticate(session.SessionID, token, now); !errors.Is(err, ErrSessionRejected) {
t.Fatalf("revoked session accepted: %v", err)
}
if _, err := store.Authenticate(session.SessionID, token, now.Add(time.Minute)); !errors.Is(err, ErrSessionRejected) {
t.Fatalf("expired session accepted: %v", err)
}
}
func TestAuthCoordinatorOnlyReleasesBackendVerifiedIdentity(t *testing.T) {
now := time.Unix(1000, 0)
verifier, _ := NewTicketVerifier(480)
coordinator := NewAuthCoordinator()
ticket := SteamTicket{TicketID: "ticket-1", SteamID: "steam-1", AppID: 480, ExpiresAt: now.Add(time.Minute)}
if err := coordinator.Begin("attempt-1", ticket, now); err != nil {
t.Fatal(err)
}
if _, err := coordinator.Get("attempt-1"); !errors.Is(err, ErrAuthAttemptPending) {
t.Fatalf("pending identity exposed: %v", err)
}
if err := coordinator.Cancel("attempt-1"); err != nil {
t.Fatal(err)
}
if _, err := coordinator.Complete("attempt-1", ticket, verifier, func(string) (string, bool) { return "player-1", true }, now); !errors.Is(err, ErrAuthAttemptRejected) {
t.Fatalf("cancelled attempt completed: %v", err)
}
if err := coordinator.Begin("attempt-2", ticket, now); err != nil {
t.Fatal(err)
}
wrongAttemptTicket := ticket
wrongAttemptTicket.TicketID = "ticket-2"
if _, err := coordinator.Complete("attempt-2", wrongAttemptTicket, verifier, func(string) (string, bool) { return "player-1", true }, now); !errors.Is(err, ErrAuthAttemptRejected) {
t.Fatalf("wrong ticket completed: %v", err)
}
identity, err := coordinator.Complete("attempt-2", ticket, verifier, func(id string) (string, bool) { return "player-1", id == "steam-1" }, now)
if err != nil || identity.PlayerID != "player-1" {
t.Fatalf("verified identity = %+v err=%v", identity, err)
}
attempt, err := coordinator.Get("attempt-2")
if err != nil || attempt.State != AuthAccepted || attempt.Identity != identity {
t.Fatalf("accepted attempt = %+v err=%v", attempt, err)
}
}
func TestAuthCoordinatorRejectsExpiredCompletion(t *testing.T) {
now := time.Unix(1000, 0)
verifier, _ := NewTicketVerifier(480)
coordinator := NewAuthCoordinator()
ticket := SteamTicket{TicketID: "ticket-1", SteamID: "steam-1", AppID: 480, ExpiresAt: now.Add(time.Second)}
if err := coordinator.Begin("attempt-1", ticket, now); err != nil {
t.Fatal(err)
}
if _, err := coordinator.Complete("attempt-1", ticket, verifier, func(string) (string, bool) { return "player-1", true }, now.Add(time.Second)); !errors.Is(err, ErrAuthAttemptRejected) {
t.Fatalf("expired attempt completed: %v", err)
}
}
func TestAuthCoordinatorExpiresAbandonedPendingAttemptsAtBoundary(t *testing.T) {
now := time.Unix(1000, 0)
coordinator := NewAuthCoordinator()
ticket := SteamTicket{TicketID: "ticket-1", ExpiresAt: now.Add(time.Second)}
if err := coordinator.Begin("attempt-1", ticket, now); err != nil {
t.Fatal(err)
}
if expired := coordinator.Expire(now.Add(time.Second)); len(expired) != 1 || expired[0].State != AuthRejected {
t.Fatalf("expired attempts = %+v", expired)
}
if _, err := coordinator.Get("attempt-1"); !errors.Is(err, ErrAuthAttemptPending) {
t.Fatalf("expired attempt was exposed: %v", err)
}
if expired := coordinator.Expire(now.Add(2 * time.Second)); len(expired) != 0 {
t.Fatalf("expired attempt repeated: %+v", expired)
}
}