feat: 修复 WebSocket 越权与 AI 流 SSRF
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
const wsTicketTTL = 60 * time.Second
|
||||
|
||||
type wsTicket struct {
|
||||
userID string
|
||||
username string
|
||||
role string
|
||||
expiresAt time.Time
|
||||
used bool
|
||||
}
|
||||
|
||||
// IssueTicket 签发一次性 WebSocket ticket,避免长期 JWT 进入 URL。
|
||||
func (h *Hub) IssueTicket(userID, username, role string) string {
|
||||
buf := make([]byte, 32)
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
return ""
|
||||
}
|
||||
ticket := hex.EncodeToString(buf)
|
||||
|
||||
h.ticketsMu.Lock()
|
||||
h.tickets[ticket] = &wsTicket{
|
||||
userID: userID,
|
||||
username: username,
|
||||
role: role,
|
||||
expiresAt: time.Now().Add(wsTicketTTL),
|
||||
}
|
||||
h.ticketsMu.Unlock()
|
||||
return ticket
|
||||
}
|
||||
|
||||
func (h *Hub) consumeTicket(ticket string) (wsTicket, bool) {
|
||||
h.ticketsMu.Lock()
|
||||
defer h.ticketsMu.Unlock()
|
||||
|
||||
info, ok := h.tickets[ticket]
|
||||
if !ok || info.used || time.Now().After(info.expiresAt) {
|
||||
return wsTicket{}, false
|
||||
}
|
||||
info.used = true
|
||||
return *info, true
|
||||
}
|
||||
|
||||
func (h *Hub) ticketFromQuery(rawQuery string) string {
|
||||
values, err := url.ParseQuery(rawQuery)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return values.Get("ticket")
|
||||
}
|
||||
Reference in New Issue
Block a user