169 lines
3.5 KiB
Go
169 lines
3.5 KiB
Go
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type Steam2FAStatus string
|
|
|
|
const (
|
|
Steam2FAStatusIdle Steam2FAStatus = "idle"
|
|
Steam2FAStatusPending Steam2FAStatus = "pending"
|
|
Steam2FAStatusComplete Steam2FAStatus = "complete"
|
|
Steam2FAStatusError Steam2FAStatus = "error"
|
|
)
|
|
|
|
type Steam2FARequest struct {
|
|
ID string `json:"id"`
|
|
Status Steam2FAStatus `json:"status"`
|
|
Message string `json:"message"`
|
|
RequestTime time.Time `json:"requestTime"`
|
|
CompletedAt *time.Time `json:"completedAt,omitempty"`
|
|
ErrorMsg string `json:"errorMsg,omitempty"`
|
|
ServerID *uuid.UUID `json:"serverId,omitempty"`
|
|
}
|
|
|
|
// Steam2FAManager manages 2FA requests and responses
|
|
type Steam2FAManager struct {
|
|
mu sync.RWMutex
|
|
requests map[string]*Steam2FARequest
|
|
channels map[string]chan bool
|
|
}
|
|
|
|
func NewSteam2FAManager() *Steam2FAManager {
|
|
return &Steam2FAManager{
|
|
requests: make(map[string]*Steam2FARequest),
|
|
channels: make(map[string]chan bool),
|
|
}
|
|
}
|
|
|
|
func (m *Steam2FAManager) CreateRequest(message string, serverID *uuid.UUID) *Steam2FARequest {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
id := uuid.New().String()
|
|
request := &Steam2FARequest{
|
|
ID: id,
|
|
Status: Steam2FAStatusPending,
|
|
Message: message,
|
|
RequestTime: time.Now(),
|
|
ServerID: serverID,
|
|
}
|
|
|
|
m.requests[id] = request
|
|
m.channels[id] = make(chan bool, 1)
|
|
|
|
return request
|
|
}
|
|
|
|
func (m *Steam2FAManager) GetRequest(id string) (*Steam2FARequest, bool) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
req, exists := m.requests[id]
|
|
return req, exists
|
|
}
|
|
|
|
func (m *Steam2FAManager) GetPendingRequests() []*Steam2FARequest {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
var pending []*Steam2FARequest
|
|
for _, req := range m.requests {
|
|
if req.Status == Steam2FAStatusPending {
|
|
pending = append(pending, req)
|
|
}
|
|
}
|
|
return pending
|
|
}
|
|
|
|
func (m *Steam2FAManager) CompleteRequest(id string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
req, exists := m.requests[id]
|
|
if !exists {
|
|
return fmt.Errorf("request %s not found", id)
|
|
}
|
|
|
|
if req.Status != Steam2FAStatusPending {
|
|
return fmt.Errorf("request %s is not pending", id)
|
|
}
|
|
|
|
now := time.Now()
|
|
req.Status = Steam2FAStatusComplete
|
|
req.CompletedAt = &now
|
|
|
|
// Signal the waiting goroutine
|
|
if ch, exists := m.channels[id]; exists {
|
|
select {
|
|
case ch <- true:
|
|
default:
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Steam2FAManager) ErrorRequest(id string, errorMsg string) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
req, exists := m.requests[id]
|
|
if !exists {
|
|
return fmt.Errorf("request %s not found", id)
|
|
}
|
|
|
|
req.Status = Steam2FAStatusError
|
|
req.ErrorMsg = errorMsg
|
|
|
|
// Signal the waiting goroutine with error
|
|
if ch, exists := m.channels[id]; exists {
|
|
select {
|
|
case ch <- false:
|
|
default:
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Steam2FAManager) WaitForCompletion(id string, timeout time.Duration) (bool, error) {
|
|
m.mu.RLock()
|
|
ch, exists := m.channels[id]
|
|
m.mu.RUnlock()
|
|
|
|
if !exists {
|
|
return false, fmt.Errorf("request %s not found", id)
|
|
}
|
|
|
|
select {
|
|
case success := <-ch:
|
|
return success, nil
|
|
case <-time.After(timeout):
|
|
// Timeout - mark as error
|
|
m.ErrorRequest(id, "timeout waiting for 2FA confirmation")
|
|
return false, fmt.Errorf("timeout waiting for 2FA confirmation")
|
|
}
|
|
}
|
|
|
|
func (m *Steam2FAManager) CleanupOldRequests(maxAge time.Duration) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
cutoff := time.Now().Add(-maxAge)
|
|
for id, req := range m.requests {
|
|
if req.RequestTime.Before(cutoff) {
|
|
delete(m.requests, id)
|
|
if ch, exists := m.channels[id]; exists {
|
|
close(ch)
|
|
delete(m.channels, id)
|
|
}
|
|
}
|
|
}
|
|
}
|