xworkspace-console/api/server.go

149 lines
4.1 KiB
Go

package main
import (
"encoding/json"
"net/http"
"runtime"
"strconv"
)
type Server struct {
serviceProbe ServiceProbe
metricProbe MetricProbe
portalServices PortalServiceProvider
auth AuthConfig
}
func NewServer() *Server {
serviceProbe := NewServiceProbe()
return &Server{
serviceProbe: serviceProbe,
metricProbe: NewMetricProbe(),
portalServices: NewPortalServiceProvider(),
auth: NewAuthConfig(),
}
}
func (s *Server) ListenAndServe(addr string) error {
return http.ListenAndServe(addr, s.withCORS(s.routes()))
}
func (s *Server) routes() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/auth/status", s.authStatusHandler)
mux.HandleFunc("/auth/reset", s.resetAuthHandler)
mux.HandleFunc("/health", s.healthHandler)
mux.HandleFunc("/services", s.servicesHandler)
mux.HandleFunc("/portal/services", s.portalServicesHandler)
mux.HandleFunc("/metrics/simple", s.metricsHandler)
return mux
}
func (s *Server) withCORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
if allowedOrigin(origin) {
w.Header().Set("Access-Control-Allow-Origin", origin)
} else {
w.Header().Set("Access-Control-Allow-Origin", "http://127.0.0.1:17000")
}
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Bridge-Token, X-XWorkspace-Token")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
func allowedOrigin(origin string) bool {
switch origin {
case "http://127.0.0.1:17000", "http://localhost:17000", "http://127.0.0.1:3000", "https://console.svc.plus":
return true
default:
return false
}
}
func (s *Server) authStatusHandler(w http.ResponseWriter, r *http.Request) {
writeJSON(w, AuthStatusResponse{Required: s.auth.Required()})
}
func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {
if !s.requireAuth(w, r) {
return
}
services := s.serviceProbe.Probe()
writeJSON(w, HealthResponse{
Status: "ok",
Arch: runtime.GOARCH,
OS: runtime.GOOS,
CPU: runtime.NumCPU(),
Memory: "unknown",
Disk: "unknown",
Services: services,
Metrics: s.metricProbe.Probe(services),
})
}
func (s *Server) servicesHandler(w http.ResponseWriter, r *http.Request) {
if !s.requireAuth(w, r) {
return
}
writeJSON(w, s.serviceProbe.Probe())
}
func (s *Server) portalServicesHandler(w http.ResponseWriter, r *http.Request) {
if !s.requireAuth(w, r) {
return
}
writeJSON(w, PortalServicesResponse{Services: s.portalServices.Services()})
}
func (s *Server) metricsHandler(w http.ResponseWriter, r *http.Request) {
if !s.requireAuth(w, r) {
return
}
_, _ = w.Write([]byte("xworkspace_systemd_services " + strconv.Itoa(len(s.serviceProbe.Probe())) + "\n"))
}
func (s *Server) requireAuth(w http.ResponseWriter, r *http.Request) bool {
if s.auth.Authorize(r) {
return true
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusUnauthorized)
_ = json.NewEncoder(w).Encode(map[string]string{"error": "unauthorized"})
return false
}
func writeJSON(w http.ResponseWriter, v any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
if err := enc.Encode(v); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func (s *Server) resetAuthHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req ResetAuthRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
newToken, err := s.auth.ResetAuthToken(req.CurrentToken)
if err != nil {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusUnauthorized)
_ = json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}
writeJSON(w, map[string]string{"token": newToken})
}