1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
// admin_handlers.go
package main
import (
"encoding/json"
"log"
"net/http"
"strconv"
"time"
"github.com/gorilla/websocket"
)
type tokenCreateRequest struct {
Name string `json:"name"`
}
type tokenCreateResponse struct {
Token string `json:"token"`
Name string `json:"name"`
}
func adminTokensHandler(hub *Hub, tokensPath string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
hub.mu.RLock()
var list []TokenConfig
for _, tc := range hub.tokens {
list = append(list, *tc)
}
hub.mu.RUnlock()
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(list)
case http.MethodPost:
var req tokenCreateRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "bad json", http.StatusBadRequest)
return
}
if req.Name == "" {
http.Error(w, "name required", http.StatusBadRequest)
return
}
token, err := generateToken()
if err != nil {
log.Printf("error generating token: %v", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
tc := &TokenConfig{Token: token, Name: req.Name}
hub.mu.Lock()
hub.tokens[token] = tc
if err := saveTokens(tokensPath, hub.tokens); err != nil {
log.Printf("warning: failed to save tokens: %v", err)
}
hub.mu.Unlock()
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(tokenCreateResponse{Token: token, Name: req.Name})
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
}
type tokenRevokeRequest struct {
Token string `json:"token"`
}
func adminTokenRevokeHandler(hub *Hub, tokensPath string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
var req tokenRevokeRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "bad json", http.StatusBadRequest)
return
}
if req.Token == "" {
http.Error(w, "token required", http.StatusBadRequest)
return
}
var toKick []*Client
hub.mu.Lock()
tc, ok := hub.tokens[req.Token]
if ok {
tc.Revoked = true
// collect clients using this token so we can kick them after unlocking
for c := range hub.clients {
if c.token == tc {
toKick = append(toKick, c)
}
}
if err := saveTokens(tokensPath, hub.tokens); err != nil {
log.Printf("warning: failed to save tokens: %v", err)
}
}
hub.mu.Unlock()
// Kick all clients outside the lock to avoid deadlocks with unregister/readPump.
for _, c := range toKick {
log.Printf("kicking client %s (%s) due to token revoke", c.nick, c.token.Name)
_ = c.conn.WriteControl(
websocket.CloseMessage,
websocket.FormatCloseMessage(websocket.ClosePolicyViolation, "token revoked"),
time.Now().Add(5*time.Second),
)
c.conn.Close()
}
w.WriteHeader(http.StatusNoContent)
}
}
func adminEventsHandler(hub *Hub) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
sinceStr := r.URL.Query().Get("since")
var since int64
if sinceStr != "" {
if v, err := strconv.ParseInt(sinceStr, 10, 64); err == nil {
since = v
}
}
events := hub.getEventsSince(since)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(events)
}
}
|