Files
AetherForge/server/internal/api/ws_ticket.go
AetherForge 8466c7aa9b fix: 2026-06-04 audit pass — README, USB pack, multi-area fixes
WS ticket dashboard auth, builder universal signing/size limits/fusion obfuscation/dropper bundles, Path Tracer WireGuard topology, SessionGate degraded mode and download timeouts, server bootstrap (data dir, cloudflared dedupe, config port precedence), agent mesh/miner/spread fixes. README refreshed; usb bundle repacked; PROBLEMS.md audit log updated.
2026-06-04 20:41:44 -07:00

55 lines
1.1 KiB
Go

package api
import (
"crypto/rand"
"encoding/hex"
"sync"
"time"
)
const wsTicketTTL = 2 * time.Minute
type wsTicketEntry struct {
Username string
ExpiresAt time.Time
}
var (
wsTicketMu sync.Mutex
wsTickets = map[string]wsTicketEntry{}
)
// issueWSTicket mints a one-time, short-lived dashboard WebSocket credential.
func issueWSTicket(username string) string {
b := make([]byte, 24)
_, _ = rand.Read(b)
ticket := hex.EncodeToString(b)
wsTicketMu.Lock()
wsTickets[ticket] = wsTicketEntry{Username: username, ExpiresAt: time.Now().Add(wsTicketTTL)}
if len(wsTickets) > 512 {
now := time.Now()
for k, v := range wsTickets {
if now.After(v.ExpiresAt) {
delete(wsTickets, k)
}
}
}
wsTicketMu.Unlock()
return ticket
}
// consumeWSTicket validates and invalidates a ticket (one-time use).
func consumeWSTicket(ticket string) (string, bool) {
wsTicketMu.Lock()
defer wsTicketMu.Unlock()
entry, ok := wsTickets[ticket]
if !ok || time.Now().After(entry.ExpiresAt) {
if ok {
delete(wsTickets, ticket)
}
return "", false
}
delete(wsTickets, ticket)
return entry.Username, true
}