rmm2/backend/api/apikeys.go
cynfo3000 ec30be7246 security: API-Key Permissions + Customer-Scoping
- DB Migration: api_keys bekommt 'permissions' + 'customer_id' Spalten
- Permissions: agent | read | write | admin
  - agent:  nur /api/v1/agent/ws + /api/v1/agent/heartbeat
  - read:   GET-Endpoints (optional customer-scoped)
  - write:  alles ausser Terminal
  - admin:  voll (Terminal bleibt JWT-only)
- Bestehende Keys werden auf 'admin' migriert (Abwaertskompatibilitaet)
- Frontend: API-Key Verwaltung zeigt Permissions-Badge + Kunden-Scope
- API-Client: createAPIKey nimmt jetzt permissions + customer_id
2026-03-09 23:52:26 +01:00

78 lines
2.2 KiB
Go

package api
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"log"
"net/http"
"strconv"
)
// GET /api/v1/apikeys
func (h *Handler) listAPIKeys(w http.ResponseWriter, r *http.Request) {
keys, err := h.db.ListAPIKeys()
if err != nil {
log.Printf("API-Keys laden fehlgeschlagen: %v", err)
writeError(w, http.StatusInternalServerError, "Laden fehlgeschlagen")
return
}
writeJSON(w, http.StatusOK, keys)
}
// POST /api/v1/apikeys
func (h *Handler) createAPIKey(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
Permissions string `json:"permissions"` // "agent" | "read" | "write" | "admin"
CustomerID *int `json:"customer_id"` // optional, null = global
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Name == "" {
writeError(w, http.StatusBadRequest, "Parameter 'name' erforderlich")
return
}
// Permissions validieren, default: "read"
validPerms := map[string]bool{"agent": true, "read": true, "write": true, "admin": true}
if req.Permissions == "" {
req.Permissions = "read"
}
if !validPerms[req.Permissions] {
writeError(w, http.StatusBadRequest, "Ungueltige Permissions (agent|read|write|admin)")
return
}
// Zufaelligen 32-Byte Key generieren
b := make([]byte, 16)
rand.Read(b)
key := hex.EncodeToString(b)
apiKey, err := h.db.CreateAPIKey(req.Name, key, req.Permissions, req.CustomerID)
if err != nil {
log.Printf("API-Key erstellen fehlgeschlagen: %v", err)
writeError(w, http.StatusInternalServerError, "Erstellen fehlgeschlagen")
return
}
log.Printf("Neuer API-Key erstellt: %s (%s...) perm=%s", apiKey.Name, key[:8], apiKey.Permissions)
writeJSON(w, http.StatusCreated, apiKey)
}
// DELETE /api/v1/apikeys/{id}
func (h *Handler) deleteAPIKey(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
writeError(w, http.StatusBadRequest, "Ungueltige ID")
return
}
if err := h.db.DeleteAPIKey(id); err != nil {
writeError(w, http.StatusNotFound, "API-Key nicht gefunden")
return
}
log.Printf("API-Key %d geloescht", id)
writeJSON(w, http.StatusOK, map[string]string{"message": "API-Key geloescht"})
}