v0.1.0
This commit is contained in:
parent
027d678a09
commit
5458c3ba86
|
@ -0,0 +1,185 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTP Basic Authentication middleware for http.HandlerFunc
|
||||||
|
// This is used for the API
|
||||||
|
func authHandlerFunc(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
// Based on https://www.alexedwards.net/blog/basic-authentication-in-go
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if config.DisableAuth {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user, pass, ok := r.BasicAuth()
|
||||||
|
if ok {
|
||||||
|
userHash := sha256.Sum256([]byte(user))
|
||||||
|
passHash := sha256.Sum256([]byte(pass))
|
||||||
|
usernameMatch := (subtle.ConstantTimeCompare(userHash[:], usernamehash[:]) == 1)
|
||||||
|
passwordMatch := (subtle.ConstantTimeCompare(passHash[:], passwordhash[:]) == 1)
|
||||||
|
if usernameMatch && passwordMatch {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(user) == 0 {
|
||||||
|
user = "-"
|
||||||
|
}
|
||||||
|
log.Println("Authentication failed: IP:", r.RemoteAddr, "URI:", r.RequestURI, "username:", user)
|
||||||
|
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
||||||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP Basic Authentication middleware for http.Handler
|
||||||
|
// Used for the HTML monitor views
|
||||||
|
func authHandler(next http.Handler) http.HandlerFunc {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if config.DisableAuth {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user, pass, ok := r.BasicAuth()
|
||||||
|
if ok {
|
||||||
|
userHash := sha256.Sum256([]byte(user))
|
||||||
|
passHash := sha256.Sum256([]byte(pass))
|
||||||
|
usernameMatch := (subtle.ConstantTimeCompare(userHash[:], usernamehash[:]) == 1)
|
||||||
|
passwordMatch := (subtle.ConstantTimeCompare(passHash[:], passwordhash[:]) == 1)
|
||||||
|
if usernameMatch && passwordMatch {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(user) == 0 {
|
||||||
|
user = "-"
|
||||||
|
}
|
||||||
|
log.Println("Authentication failed: IP:", r.RemoteAddr, "URI:", r.RequestURI, "username:", user)
|
||||||
|
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
||||||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// /api/v1/coils endpoint
|
||||||
|
func coils(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
pathparams := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v1/coils/"), "/")
|
||||||
|
if len(pathparams[0]) == 0 {
|
||||||
|
_ = json.NewEncoder(w).Encode(device.Coils)
|
||||||
|
} else if len(pathparams[0]) > 0 && r.Method == "GET" && len(pathparams) < 2 { // && r.Method == "POST"
|
||||||
|
intaddr, err := strconv.Atoi(pathparams[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ERROR: Could not parse coil address", pathparams[0])
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = device.ReadCoil(uint16(intaddr))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ERROR ReadCoil: client.ReadCoils: ", err)
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(device.Coils[intaddr])
|
||||||
|
} else if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 2 {
|
||||||
|
intaddr, err := strconv.Atoi(pathparams[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ERROR: Could not parse coil address", pathparams[0])
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
boolval, err := strconv.ParseBool(pathparams[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ERROR: Could not parse coil value", pathparams[1])
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if config.ReadOnly {
|
||||||
|
log.Println("WARNING: Read only mode, refusing to write to device")
|
||||||
|
} else {
|
||||||
|
device.WriteCoil(uint16(intaddr), boolval)
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(device.Coils[intaddr])
|
||||||
|
} else if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 1 {
|
||||||
|
intaddr, err := strconv.Atoi(pathparams[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ERROR: Could not parse coil address", pathparams[0])
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if config.ReadOnly {
|
||||||
|
log.Println("WARNING: Read only mode, refusing to write to device")
|
||||||
|
} else {
|
||||||
|
device.WriteCoil(uint16(intaddr), !device.Coils[intaddr].Value)
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(device.Coils[intaddr])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// /api/v1/registers endpoint
|
||||||
|
func registers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
pathparams := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v1/registers/"), "/")
|
||||||
|
if len(pathparams[0]) == 0 {
|
||||||
|
_ = json.NewEncoder(w).Encode(device.Registers)
|
||||||
|
} else if len(pathparams[0]) > 0 && r.Method == "GET" && len(pathparams) < 2 { // && r.Method == "POST"
|
||||||
|
intaddr, err := strconv.Atoi(pathparams[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ERROR: Could not parse register address", pathparams[0])
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = device.ReadRegister(uint16(intaddr))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ERROR: ReadRegister:", err)
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(device.Registers[intaddr])
|
||||||
|
} else if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 2 {
|
||||||
|
intaddr, err := strconv.Atoi(pathparams[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ERROR: Could not parse register address", pathparams[0])
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
intval, err := strconv.Atoi(pathparams[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ERROR: Could not parse register value", pathparams[1])
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if config.ReadOnly {
|
||||||
|
log.Println("WARNING: Read only mode, refusing to write to device")
|
||||||
|
} else {
|
||||||
|
_, err = device.WriteRegister(uint16(intaddr), uint16(intval))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(device.Registers[intaddr])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// /status endpoint
|
||||||
|
func status(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_ = json.NewEncoder(w).Encode(device.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// /api/v1/temperature endpoint
|
||||||
|
func temperature(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
pathparams := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v1/temperature/"), "/")
|
||||||
|
if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 1 {
|
||||||
|
err := device.Temperature(pathparams[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ERROR: ", err)
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(device.Registers[135])
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
178
main.go
178
main.go
|
@ -2,16 +2,12 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/subtle"
|
|
||||||
"embed"
|
"embed"
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
"flag"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/0ranki/enervent-ctrl/pingvin"
|
"github.com/0ranki/enervent-ctrl/pingvin"
|
||||||
|
@ -52,180 +48,6 @@ type Conf struct {
|
||||||
ReadOnly bool `yaml:"read_only"`
|
ReadOnly bool `yaml:"read_only"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP Basic Authentication middleware for http.HandlerFunc
|
|
||||||
// This is used for the API
|
|
||||||
func authHandlerFunc(next http.HandlerFunc) http.HandlerFunc {
|
|
||||||
// Based on https://www.alexedwards.net/blog/basic-authentication-in-go
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if config.DisableAuth {
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user, pass, ok := r.BasicAuth()
|
|
||||||
if ok {
|
|
||||||
userHash := sha256.Sum256([]byte(user))
|
|
||||||
passHash := sha256.Sum256([]byte(pass))
|
|
||||||
usernameMatch := (subtle.ConstantTimeCompare(userHash[:], usernamehash[:]) == 1)
|
|
||||||
passwordMatch := (subtle.ConstantTimeCompare(passHash[:], passwordhash[:]) == 1)
|
|
||||||
if usernameMatch && passwordMatch {
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(user) == 0 {
|
|
||||||
user = "-"
|
|
||||||
}
|
|
||||||
log.Println("Authentication failed: IP:", r.RemoteAddr, "URI:", r.RequestURI, "username:", user)
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
|
||||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP Basic Authentication middleware for http.Handler
|
|
||||||
// Used for the HTML monitor views
|
|
||||||
func authHandler(next http.Handler) http.HandlerFunc {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if config.DisableAuth {
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user, pass, ok := r.BasicAuth()
|
|
||||||
if ok {
|
|
||||||
userHash := sha256.Sum256([]byte(user))
|
|
||||||
passHash := sha256.Sum256([]byte(pass))
|
|
||||||
usernameMatch := (subtle.ConstantTimeCompare(userHash[:], usernamehash[:]) == 1)
|
|
||||||
passwordMatch := (subtle.ConstantTimeCompare(passHash[:], passwordhash[:]) == 1)
|
|
||||||
if usernameMatch && passwordMatch {
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(user) == 0 {
|
|
||||||
user = "-"
|
|
||||||
}
|
|
||||||
log.Println("Authentication failed: IP:", r.RemoteAddr, "URI:", r.RequestURI, "username:", user)
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
|
||||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// \/api/v1/coils endpoint
|
|
||||||
func coils(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
pathparams := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v1/coils/"), "/")
|
|
||||||
if len(pathparams[0]) == 0 {
|
|
||||||
_ = json.NewEncoder(w).Encode(device.Coils)
|
|
||||||
} else if len(pathparams[0]) > 0 && r.Method == "GET" && len(pathparams) < 2 { // && r.Method == "POST"
|
|
||||||
intaddr, err := strconv.Atoi(pathparams[0])
|
|
||||||
if err != nil {
|
|
||||||
log.Println("ERROR: Could not parse coil address", pathparams[0])
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = device.ReadCoil(uint16(intaddr))
|
|
||||||
if err != nil {
|
|
||||||
log.Println("ERROR ReadCoil: client.ReadCoils: ", err)
|
|
||||||
}
|
|
||||||
_ = json.NewEncoder(w).Encode(device.Coils[intaddr])
|
|
||||||
} else if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 2 {
|
|
||||||
intaddr, err := strconv.Atoi(pathparams[0])
|
|
||||||
if err != nil {
|
|
||||||
log.Println("ERROR: Could not parse coil address", pathparams[0])
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
boolval, err := strconv.ParseBool(pathparams[1])
|
|
||||||
if err != nil {
|
|
||||||
log.Println("ERROR: Could not parse coil value", pathparams[1])
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if config.ReadOnly {
|
|
||||||
log.Println("WARNING: Read only mode, refusing to write to device")
|
|
||||||
} else {
|
|
||||||
device.WriteCoil(uint16(intaddr), boolval)
|
|
||||||
}
|
|
||||||
_ = json.NewEncoder(w).Encode(device.Coils[intaddr])
|
|
||||||
} else if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 1 {
|
|
||||||
intaddr, err := strconv.Atoi(pathparams[0])
|
|
||||||
if err != nil {
|
|
||||||
log.Println("ERROR: Could not parse coil address", pathparams[0])
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if config.ReadOnly {
|
|
||||||
log.Println("WARNING: Read only mode, refusing to write to device")
|
|
||||||
} else {
|
|
||||||
device.WriteCoil(uint16(intaddr), !device.Coils[intaddr].Value)
|
|
||||||
}
|
|
||||||
_ = json.NewEncoder(w).Encode(device.Coils[intaddr])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// \/api/v1/registers endpoint
|
|
||||||
func registers(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
pathparams := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v1/registers/"), "/")
|
|
||||||
if len(pathparams[0]) == 0 {
|
|
||||||
_ = json.NewEncoder(w).Encode(device.Registers)
|
|
||||||
} else if len(pathparams[0]) > 0 && r.Method == "GET" && len(pathparams) < 2 { // && r.Method == "POST"
|
|
||||||
intaddr, err := strconv.Atoi(pathparams[0])
|
|
||||||
if err != nil {
|
|
||||||
log.Println("ERROR: Could not parse register address", pathparams[0])
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = device.ReadRegister(uint16(intaddr))
|
|
||||||
if err != nil {
|
|
||||||
log.Println("ERROR: ReadRegister:", err)
|
|
||||||
}
|
|
||||||
_ = json.NewEncoder(w).Encode(device.Registers[intaddr])
|
|
||||||
} else if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 2 {
|
|
||||||
intaddr, err := strconv.Atoi(pathparams[0])
|
|
||||||
if err != nil {
|
|
||||||
log.Println("ERROR: Could not parse register address", pathparams[0])
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
intval, err := strconv.Atoi(pathparams[1])
|
|
||||||
if err != nil {
|
|
||||||
log.Println("ERROR: Could not parse register value", pathparams[1])
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if config.ReadOnly {
|
|
||||||
log.Println("WARNING: Read only mode, refusing to write to device")
|
|
||||||
} else {
|
|
||||||
_, err = device.WriteRegister(uint16(intaddr), uint16(intval))
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ = json.NewEncoder(w).Encode(device.Registers[intaddr])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// \/status endpoint
|
|
||||||
func status(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
_ = json.NewEncoder(w).Encode(device.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// \/api/v1/temperature endpoint
|
|
||||||
func temperature(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
pathparams := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v1/temperature/"), "/")
|
|
||||||
if len(pathparams[0]) > 0 && r.Method == "POST" && len(pathparams) == 1 {
|
|
||||||
err := device.Temperature(pathparams[0])
|
|
||||||
if err != nil {
|
|
||||||
log.Println("ERROR: ", err)
|
|
||||||
}
|
|
||||||
_ = json.NewEncoder(w).Encode(device.Registers[135])
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the HTTP server
|
// Start the HTTP server
|
||||||
func serve(cert, key *string) {
|
func serve(cert, key *string) {
|
||||||
log.Println("Starting service")
|
log.Println("Starting service")
|
||||||
|
|
Loading…
Reference in New Issue