// Package server wires jarvis's HTTP surface: the Grafana webhook // receiver, a background diagnostic worker, and the authenticated Atom feed. package server import ( "crypto/subtle" "net/http" ) // Config holds the server's runtime configuration. type Config struct { // WebhookToken authenticates Grafana -> jarvis on POST /webhook/grafana. // Sent by Grafana as a Bearer token (Authorization: Bearer ). WebhookToken string // FeedUser / FeedPassword gate the Atom feed with HTTP Basic auth // (what miniflux connects with). FeedUser string FeedPassword string // FeedTitle / FeedID / BaseURL describe the feed document. FeedTitle string FeedID string BaseURL string // FeedLimit caps how many entries the feed serves. FeedLimit int } // bearerAuth wraps h, requiring "Authorization: Bearer ". If token is // empty the endpoint is left open (useful for local dev only). func bearerAuth(token string, h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if token != "" { const prefix = "Bearer " got := r.Header.Get("Authorization") if len(got) <= len(prefix) || subtle.ConstantTimeCompare([]byte(got[len(prefix):]), []byte(token)) != 1 { http.Error(w, "unauthorized", http.StatusUnauthorized) return } } h(w, r) } } // basicAuth wraps h with HTTP Basic auth. If user is empty the endpoint is // left open (local dev only). func basicAuth(user, pass string, h http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if user != "" { u, p, ok := r.BasicAuth() userOK := subtle.ConstantTimeCompare([]byte(u), []byte(user)) == 1 passOK := subtle.ConstantTimeCompare([]byte(p), []byte(pass)) == 1 if !ok || !userOK || !passOK { w.Header().Set("WWW-Authenticate", `Basic realm="jarvis"`) http.Error(w, "unauthorized", http.StatusUnauthorized) return } } h(w, r) } }