summaryrefslogtreecommitdiff
path: root/admin_handlers.go
diff options
context:
space:
mode:
authorplutorocks <>2026-02-26 21:30:32 +0000
committerplutorocks <>2026-02-26 21:30:32 +0000
commit3db298ec3eca0ed94cb7912f660df7dd1f4582e0 (patch)
treed406e1d64aa10c8027bf4bf4a5914f1378fb495e /admin_handlers.go
initial commitHEADmaster
Diffstat (limited to 'admin_handlers.go')
-rw-r--r--admin_handlers.go135
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