From 4dc8bcefb91b578f843683bad8d6df976d28f3ac Mon Sep 17 00:00:00 2001 From: Jordan Hotmann Date: Fri, 8 Dec 2023 16:37:54 -0700 Subject: [PATCH] API basic auth support --- internal/api/api.go | 28 ++++++++++----- internal/nats/client.go | 2 +- pkg/purpleair/structs.go | 76 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 pkg/purpleair/structs.go diff --git a/internal/api/api.go b/internal/api/api.go index fc25f3c..b2a39da 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -60,7 +60,7 @@ func Listen(parentLogger *slog.Logger, readme []byte) { }) router.Route("/api", func(r chi.Router) { - r.Use(tokenAuthMiddleware) + r.Use(authMiddleware) r.Get(`/state/{entityId}`, getEntityStateHandler) r.Post("/state/{entityId}/{service}", setEntityStateHandler) @@ -91,20 +91,32 @@ func Close() { } } -func tokenAuthMiddleware(next http.Handler) http.Handler { +// authMiddleware checks both basic and bearer auth schemes for a token +// +// When using basic auth: the username does not matter and the the password should equal the configured token +// When using bearer auth: set the "Authorization" header to "Bearer your-token-here" +func authMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if cfg.HatsToken == "" { // No token required next.ServeHTTP(w, r) return } - authHeaderParts := strings.Split(r.Header.Get("Authorization"), " ") - if len(authHeaderParts) != 2 || authHeaderParts[0] != "Bearer" || authHeaderParts[1] != cfg.HatsToken { - logger.Warn("Unauthorized request", "method", r.Method, "path", r.URL.Path, "address", r.RemoteAddr) - http.Error(w, "Bearer authorization header doesn't match configured token", http.StatusUnauthorized) + _, p, ok := r.BasicAuth() + + if ok && p == cfg.HatsToken { + next.ServeHTTP(w, r) return } - next.ServeHTTP(w, r) + + authHeaderPars := strings.SplitN(r.Header.Get("Authorization"), " ", 2) + if len(authHeaderPars) > 1 && strings.EqualFold(authHeaderPars[0], "bearer") && authHeaderPars[1] == cfg.HatsToken { + next.ServeHTTP(w, r) + return + } + + logger.Warn("Unauthorized request", "method", r.Method, "path", r.URL.Path, "address", r.RemoteAddr) + http.Error(w, "Bearer authorization header doesn't match configured token", http.StatusUnauthorized) }) } @@ -152,7 +164,7 @@ func setEntityStateHandler(w http.ResponseWriter, r *http.Request) { entityId := chi.URLParam(r, "entityId") service := chi.URLParam(r, "service") domain := r.URL.Query().Get("domain") - + l := logger.With("endpoint", "POST /api/state/{entityId}/{service}", "entityId", entityId, "service", service, "domain", domain) var extras map[string]any diff --git a/internal/nats/client.go b/internal/nats/client.go index 5e61c7e..9a0119a 100644 --- a/internal/nats/client.go +++ b/internal/nats/client.go @@ -88,7 +88,7 @@ func PublishRequest(subject string, message []byte, timeout time.Duration, retri for { attempts += 1 if attempts > retries { - logger.Error("Request retries exceeded", "subject", subject) + logger.Warn("Request retries exceeded", "subject", subject) return } resp, err := client.Conn.Request(subject, message, timeout) diff --git a/pkg/purpleair/structs.go b/pkg/purpleair/structs.go new file mode 100644 index 0000000..5dd045a --- /dev/null +++ b/pkg/purpleair/structs.go @@ -0,0 +1,76 @@ +package purpleair + +type WebhookPayload struct { + SensorID string `json:"SensorId,omitempty"` + DateTime string `json:"DateTime,omitempty"` + Geo string `json:"Geo,omitempty"` + Mem int `json:"Mem,omitempty"` + Memfrag int `json:"memfrag,omitempty"` + Memfb int `json:"memfb,omitempty"` + Memcs int `json:"memcs,omitempty"` + ID int `json:"Id,omitempty"` + Lat float64 `json:"lat,omitempty"` + Lon float64 `json:"lon,omitempty"` + Adc float64 `json:"Adc,omitempty"` + Loggingrate int `json:"loggingrate,omitempty"` + Place string `json:"place,omitempty"` + Version string `json:"version,omitempty"` + Uptime int `json:"uptime,omitempty"` + Rssi int `json:"rssi,omitempty"` + Period int `json:"period,omitempty"` + Httpsuccess int `json:"httpsuccess,omitempty"` + Httpsends int `json:"httpsends,omitempty"` + Hardwareversion string `json:"hardwareversion,omitempty"` + Hardwarediscovered string `json:"hardwarediscovered,omitempty"` + CurrentTempF int `json:"current_temp_f,omitempty"` + CurrentHumidity int `json:"current_humidity,omitempty"` + CurrentDewpointF int `json:"current_dewpoint_f,omitempty"` + Pressure float64 `json:"pressure,omitempty"` + P25AqicB string `json:"p25aqic_b,omitempty"` + Pm25AqiB int `json:"pm2.5_aqi_b,omitempty"` + Pm10Cf1B float64 `json:"pm1_0_cf_1_b,omitempty"` + P03UmB float64 `json:"p_0_3_um_b,omitempty"` + Pm25Cf1B float64 `json:"pm2_5_cf_1_b,omitempty"` + P05UmB float64 `json:"p_0_5_um_b,omitempty"` + Pm100Cf1B float64 `json:"pm10_0_cf_1_b,omitempty"` + P10UmB float64 `json:"p_1_0_um_b,omitempty"` + Pm10AtmB float64 `json:"pm1_0_atm_b,omitempty"` + P25UmB float64 `json:"p_2_5_um_b,omitempty"` + Pm25AtmB float64 `json:"pm2_5_atm_b,omitempty"` + P50UmB float64 `json:"p_5_0_um_b,omitempty"` + Pm100AtmB float64 `json:"pm10_0_atm_b,omitempty"` + P100UmB float64 `json:"p_10_0_um_b,omitempty"` + P25Aqic string `json:"p25aqic,omitempty"` + Pm25Aqi int `json:"pm2.5_aqi,omitempty"` + Pm10Cf1 float64 `json:"pm1_0_cf_1,omitempty"` + P03Um float64 `json:"p_0_3_um,omitempty"` + Pm25Cf1 float64 `json:"pm2_5_cf_1,omitempty"` + P05Um float64 `json:"p_0_5_um,omitempty"` + Pm100Cf1 float64 `json:"pm10_0_cf_1,omitempty"` + P10Um float64 `json:"p_1_0_um,omitempty"` + Pm10Atm float64 `json:"pm1_0_atm,omitempty"` + P25Um float64 `json:"p_2_5_um,omitempty"` + Pm25Atm float64 `json:"pm2_5_atm,omitempty"` + P50Um float64 `json:"p_5_0_um,omitempty"` + Pm100Atm float64 `json:"pm10_0_atm,omitempty"` + P100Um float64 `json:"p_10_0_um,omitempty"` + PaLatency int `json:"pa_latency,omitempty"` + Response int `json:"response,omitempty"` + ResponseDate int `json:"response_date,omitempty"` + Latency int `json:"latency,omitempty"` + ResponseB int `json:"response_b,omitempty"` + ResponseDateB int `json:"response_date_b,omitempty"` + LatencyB int `json:"latency_b,omitempty"` + Wlstate string `json:"wlstate,omitempty"` + Status0 int `json:"status_0,omitempty"` + Status1 int `json:"status_1,omitempty"` + Status2 int `json:"status_2,omitempty"` + Status3 int `json:"status_3,omitempty"` + Status4 int `json:"status_4,omitempty"` + Status5 int `json:"status_5,omitempty"` + Status6 int `json:"status_6,omitempty"` + Status7 int `json:"status_7,omitempty"` + Status8 int `json:"status_8,omitempty"` + Status9 int `json:"status_9,omitempty"` + Status10 int `json:"status_10,omitempty"` +}