mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-13 02:42:03 +00:00
feat: model asynchronous Steam auth sessions
This commit is contained in:
@@ -28,6 +28,108 @@ type TicketVerifier struct {
|
||||
consumed map[string]time.Time
|
||||
}
|
||||
|
||||
type AuthAttemptState string
|
||||
|
||||
const (
|
||||
AuthPending AuthAttemptState = "PENDING"
|
||||
AuthAccepted AuthAttemptState = "ACCEPTED"
|
||||
AuthRejected AuthAttemptState = "REJECTED"
|
||||
AuthCancelled AuthAttemptState = "CANCELLED"
|
||||
)
|
||||
|
||||
type AuthAttempt struct {
|
||||
AttemptID string
|
||||
TicketID string
|
||||
State AuthAttemptState
|
||||
Identity VerifiedIdentity
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
// AuthCoordinator models the asynchronous BeginAuthSession lifecycle. The
|
||||
// external Steam adapter calls Complete only after Steam confirms the ticket;
|
||||
// clients never supply the verified identity or transition state themselves.
|
||||
type AuthCoordinator struct {
|
||||
mu sync.Mutex
|
||||
attempts map[string]AuthAttempt
|
||||
}
|
||||
|
||||
var (
|
||||
ErrAuthAttemptRejected = fmt.Errorf("auth attempt rejected")
|
||||
ErrAuthAttemptPending = fmt.Errorf("auth attempt pending")
|
||||
)
|
||||
|
||||
func NewAuthCoordinator() *AuthCoordinator {
|
||||
return &AuthCoordinator{attempts: make(map[string]AuthAttempt)}
|
||||
}
|
||||
|
||||
func (c *AuthCoordinator) Begin(attemptID string, ticket SteamTicket, now time.Time) error {
|
||||
if c == nil || attemptID == "" || ticket.TicketID == "" || ticket.ExpiresAt.IsZero() || !now.Before(ticket.ExpiresAt) {
|
||||
return ErrAuthAttemptRejected
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if _, exists := c.attempts[attemptID]; exists {
|
||||
return ErrAuthAttemptRejected
|
||||
}
|
||||
c.attempts[attemptID] = AuthAttempt{AttemptID: attemptID, TicketID: ticket.TicketID, State: AuthPending, ExpiresAt: ticket.ExpiresAt}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AuthCoordinator) Complete(attemptID string, ticket SteamTicket, verifier *TicketVerifier, resolve func(string) (string, bool), now time.Time) (VerifiedIdentity, error) {
|
||||
if c == nil || verifier == nil {
|
||||
return VerifiedIdentity{}, ErrAuthAttemptRejected
|
||||
}
|
||||
c.mu.Lock()
|
||||
attempt, ok := c.attempts[attemptID]
|
||||
if !ok || attempt.State != AuthPending || attempt.TicketID != ticket.TicketID || !now.Before(attempt.ExpiresAt) {
|
||||
c.mu.Unlock()
|
||||
return VerifiedIdentity{}, ErrAuthAttemptRejected
|
||||
}
|
||||
identity, err := verifier.Verify(ticket, resolve, now)
|
||||
if err != nil {
|
||||
attempt.State = AuthRejected
|
||||
c.attempts[attemptID] = attempt
|
||||
c.mu.Unlock()
|
||||
return VerifiedIdentity{}, ErrAuthAttemptRejected
|
||||
}
|
||||
attempt.State = AuthAccepted
|
||||
attempt.Identity = identity
|
||||
c.attempts[attemptID] = attempt
|
||||
c.mu.Unlock()
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
func (c *AuthCoordinator) Cancel(attemptID string) error {
|
||||
if c == nil || attemptID == "" {
|
||||
return ErrAuthAttemptRejected
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
attempt, ok := c.attempts[attemptID]
|
||||
if !ok || attempt.State != AuthPending {
|
||||
return ErrAuthAttemptRejected
|
||||
}
|
||||
attempt.State = AuthCancelled
|
||||
c.attempts[attemptID] = attempt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AuthCoordinator) Get(attemptID string) (AuthAttempt, error) {
|
||||
if c == nil {
|
||||
return AuthAttempt{}, ErrAuthAttemptRejected
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
attempt, ok := c.attempts[attemptID]
|
||||
if !ok {
|
||||
return AuthAttempt{}, ErrAuthAttemptRejected
|
||||
}
|
||||
if attempt.State != AuthAccepted {
|
||||
return attempt, ErrAuthAttemptPending
|
||||
}
|
||||
return attempt, nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrTicketRejected = fmt.Errorf("steam ticket rejected")
|
||||
ErrSessionRejected = fmt.Errorf("session rejected")
|
||||
|
||||
Reference in New Issue
Block a user