diff options
Diffstat (limited to 'admin_handlers.go')
| -rw-r--r-- | admin_handlers.go | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/admin_handlers.go b/admin_handlers.go new file mode 100644 index 0000000..383cebe --- /dev/null +++ b/admin_handlers.go @@ -0,0 +1,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) + } +}
\ No newline at end of file |
