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.
55 lines
1.1 KiB
Go
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
|
|
}
|