125 lines
3.4 KiB
Go
125 lines
3.4 KiB
Go
package admin
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log/slog"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// Handler bundles admin HTTP handlers and their dependencies.
|
|
type Handler struct {
|
|
store *UserStore
|
|
syncWriter *SyncWriter
|
|
logger *slog.Logger
|
|
}
|
|
|
|
// NewHandler creates a new admin Handler.
|
|
func NewHandler(store *UserStore, syncWriter *SyncWriter, logger *slog.Logger) *Handler {
|
|
return &Handler{
|
|
store: store,
|
|
syncWriter: syncWriter,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// RegisterRoutes mounts admin routes on the given mux.
|
|
func (h *Handler) RegisterRoutes(mux *http.ServeMux, authMiddleware func(http.Handler) http.Handler) {
|
|
// Admin API (protected by bearer token)
|
|
mux.Handle("GET /admin/api/users", authMiddleware(http.HandlerFunc(h.listUsers)))
|
|
mux.Handle("POST /admin/api/users", authMiddleware(http.HandlerFunc(h.createUsers)))
|
|
mux.Handle("DELETE /admin/api/users/{username}", authMiddleware(http.HandlerFunc(h.deleteUser)))
|
|
mux.Handle("GET /admin/api/health", authMiddleware(http.HandlerFunc(h.adminHealth)))
|
|
}
|
|
|
|
// --- API Handlers ---
|
|
|
|
func (h *Handler) listUsers(w http.ResponseWriter, r *http.Request) {
|
|
users, err := h.store.List()
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
if users == nil {
|
|
users = []User{}
|
|
}
|
|
writeJSON(w, http.StatusOK, users)
|
|
}
|
|
|
|
type createUsersResponse struct {
|
|
Results []CreateUserResult `json:"results"`
|
|
}
|
|
|
|
func (h *Handler) createUsers(w http.ResponseWriter, r *http.Request) {
|
|
var req CreateUserRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON body"})
|
|
return
|
|
}
|
|
|
|
if len(req.Users) == 0 {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "no users provided"})
|
|
return
|
|
}
|
|
|
|
results := h.store.Create(req)
|
|
|
|
// Sync to Authelia YAML
|
|
if err := h.syncWriter.Sync(); err != nil {
|
|
h.logger.Error("sync failed after create", "error", err)
|
|
}
|
|
|
|
writeJSON(w, http.StatusCreated, createUsersResponse{Results: results})
|
|
}
|
|
|
|
func (h *Handler) deleteUser(w http.ResponseWriter, r *http.Request) {
|
|
username := r.PathValue("username")
|
|
if username == "" {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "username is required"})
|
|
return
|
|
}
|
|
|
|
if err := h.store.Delete(username); err != nil {
|
|
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Sync to Authelia YAML
|
|
if err := h.syncWriter.Sync(); err != nil {
|
|
h.logger.Error("sync failed after delete", "error", err)
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted", "username": username})
|
|
}
|
|
|
|
func (h *Handler) adminHealth(w http.ResponseWriter, r *http.Request) {
|
|
count, err := h.store.Count()
|
|
status := "ok"
|
|
if err != nil {
|
|
status = "degraded"
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
|
"status": status,
|
|
"user_count": count,
|
|
"authelia_db": h.syncWriter.usersDBPath,
|
|
})
|
|
}
|
|
|
|
// --- Helpers ---
|
|
|
|
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
json.NewEncoder(w).Encode(data)
|
|
}
|
|
|
|
// ErrorResponse is a generic error response.
|
|
type ErrorResponse struct {
|
|
Error string `json:"error"`
|
|
}
|
|
|
|
// IsHTMLRequest checks if the client expects HTML (for HTMX routing).
|
|
func IsHTMLRequest(r *http.Request) bool {
|
|
accept := r.Header.Get("Accept")
|
|
return strings.Contains(accept, "text/html") || r.Header.Get("HX-Request") != ""
|
|
}
|