1
0
Fork 0

API basic auth support

main
Jordan Hotmann 2023-12-08 16:37:54 -07:00
parent 630d85487c
commit 4dc8bcefb9
No known key found for this signature in database
GPG Key ID: 01B504170C2A2EA3
3 changed files with 97 additions and 9 deletions

View File

@ -60,7 +60,7 @@ func Listen(parentLogger *slog.Logger, readme []byte) {
}) })
router.Route("/api", func(r chi.Router) { router.Route("/api", func(r chi.Router) {
r.Use(tokenAuthMiddleware) r.Use(authMiddleware)
r.Get(`/state/{entityId}`, getEntityStateHandler) r.Get(`/state/{entityId}`, getEntityStateHandler)
r.Post("/state/{entityId}/{service}", setEntityStateHandler) 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) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if cfg.HatsToken == "" { // No token required if cfg.HatsToken == "" { // No token required
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
return return
} }
authHeaderParts := strings.Split(r.Header.Get("Authorization"), " ") _, p, ok := r.BasicAuth()
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) if ok && p == cfg.HatsToken {
http.Error(w, "Bearer authorization header doesn't match configured token", http.StatusUnauthorized) next.ServeHTTP(w, r)
return 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") entityId := chi.URLParam(r, "entityId")
service := chi.URLParam(r, "service") service := chi.URLParam(r, "service")
domain := r.URL.Query().Get("domain") domain := r.URL.Query().Get("domain")
l := logger.With("endpoint", "POST /api/state/{entityId}/{service}", "entityId", entityId, "service", service, "domain", domain) l := logger.With("endpoint", "POST /api/state/{entityId}/{service}", "entityId", entityId, "service", service, "domain", domain)
var extras map[string]any var extras map[string]any

View File

@ -88,7 +88,7 @@ func PublishRequest(subject string, message []byte, timeout time.Duration, retri
for { for {
attempts += 1 attempts += 1
if attempts > retries { if attempts > retries {
logger.Error("Request retries exceeded", "subject", subject) logger.Warn("Request retries exceeded", "subject", subject)
return return
} }
resp, err := client.Conn.Request(subject, message, timeout) resp, err := client.Conn.Request(subject, message, timeout)

76
pkg/purpleair/structs.go Normal file
View File

@ -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"`
}