diff --git a/go.mod b/go.mod index b517b7a..b471ed2 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,23 @@ module dynatron.me/x/blasphem go 1.18 -require github.com/spf13/cobra v1.5.0 +require ( + github.com/gorilla/websocket v1.5.0 + github.com/labstack/echo/v4 v4.9.0 + github.com/spf13/cobra v1.5.0 + gopkg.in/yaml.v3 v3.0.1 +) require ( - github.com/gorilla/websocket v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/labstack/gommon v0.3.1 // indirect + github.com/mattn/go-colorable v0.1.11 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/spf13/pflag v1.0.5 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.1 // indirect + golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect + golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect + golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect + golang.org/x/text v0.3.7 // indirect ) diff --git a/go.sum b/go.sum index 068bfb5..6caacae 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,47 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY= +github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= +github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= +github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/frontend/frontend.go b/pkg/frontend/frontend.go index d6b40a8..14a40e1 100644 --- a/pkg/frontend/frontend.go +++ b/pkg/frontend/frontend.go @@ -2,7 +2,23 @@ package frontend import ( "embed" + "io/fs" + "net/http" ) //go:embed frontend/hass_frontend -var Root embed.FS +var root embed.FS + +var RootFS fs.FS + +var FSHandler http.Handler + +func init() { + var err error + RootFS, err = fs.Sub(root, "frontend/hass_frontend") + if err != nil { + panic(err) + } + + FSHandler = http.FileServer(http.FS(RootFS)) +} diff --git a/pkg/server/authorize.go b/pkg/server/authorize.go deleted file mode 100644 index f5142bd..0000000 --- a/pkg/server/authorize.go +++ /dev/null @@ -1,161 +0,0 @@ -package server - -import ( - "crypto/rand" - "encoding/hex" - "encoding/json" - "io" - "net/http" -) - -type AuthProvider interface { - ProviderName() string - ProviderID() *string - ProviderType() string -} - -type AuthProviderBase struct { - Name string `json:"name"` - ID *string `json:"id"` - Type string `json:"type"` -} - -func (bp *AuthProviderBase) ProviderName() string { return bp.Name } -func (bp *AuthProviderBase) ProviderID() *string { return bp.ID } -func (bp *AuthProviderBase) ProviderType() string { return bp.Type } - -type LocalProvider struct { - AuthProviderBase -} - -var HomeAssistant = "homeassistant" - -func hassProvider() *LocalProvider { - return &LocalProvider{ - AuthProviderBase: AuthProviderBase{ - Name: "Home Assistant Local", - Type: HomeAssistant, - }, - } -} - -// TODO: make this configurable -func (s *Server) providersHandler(w http.ResponseWriter, r *http.Request) { - providers := []AuthProvider{ - hassProvider(), - } - - rjs, err := json.Marshal(providers) - if err != nil { - panic(err) - } - - logRequest(http.StatusOK, r) - - w.Header()["Content-Type"] = []string{"application/json"} - _, err = w.Write(rjs) - if err != nil { - panic(err) - } -} - -func (s *Server) authorizeHandler(w http.ResponseWriter, r *http.Request) { - authPage, err := s.rootFS.Open("authorize.html") - if err != nil { - panic(err) - } - defer authPage.Close() - - logRequest(http.StatusOK, r) - - _, err = io.Copy(w, authPage) - if err != nil { - panic(err) - } -} - -type flowRequest struct { - ClientID string `json:"client_id"` - Handler []*string `json:"handler"` - RedirectURI string `json:"redirect_uri"` -} - -type FlowSchemaItem struct { - Type string `json:"type"` - Name string `json:"name"` - Required bool `json:"required"` -} - -type FlowType string - -const ( - TypeForm FlowType = "form" -) - -type FlowID string -type Step string - -const ( - StepInit Step = "init" -) - -type flowResponse struct { - Type FlowType `json:"type"` - ID FlowID `json:"flow_id"` - Handler []*string `json:"handler"` - StepID Step `json:"step_id"` - Schema []FlowSchemaItem `json:"data_schema"` - Errors []string `json:"errors"` - DescPlace *string `json:"description_placeholders"` - LastStep *string `json:"last_step"` -} - -func GenFlowID() FlowID { - b := make([]byte, 16) - if _, err := rand.Read(b); err != nil { - panic(err) - } - - return FlowID(hex.EncodeToString(b)) -} - -func (s *Server) loginFlowHandler(w http.ResponseWriter, r *http.Request) { - var flowReq flowRequest - err := json.NewDecoder(r.Body).Decode(&flowReq) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - - return - } - - resp := flowResponse{ - Type: TypeForm, - ID: GenFlowID(), - StepID: StepInit, - Schema: []FlowSchemaItem{ - { - Type: "string", - Name: "username", - Required: true, - }, - { - Type: "string", - Name: "password", - Required: true, - }, - }, - Handler: []*string{ - &HomeAssistant, - nil, - }, - Errors: []string{}, - } - - w.Header()["Content-Type"] = []string{"application/json"} - - respByte, err := json.Marshal(&resp) - _, err = w.Write(respByte) - if err != nil { - panic(err) - } -} diff --git a/pkg/server/server.go b/pkg/server/server.go index f338488..75d6bfa 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -6,14 +6,18 @@ import ( "net/http" "sync" + "github.com/labstack/echo/v4" + + "dynatron.me/x/blasphem/pkg/auth" "dynatron.me/x/blasphem/pkg/bus" "dynatron.me/x/blasphem/pkg/config" "dynatron.me/x/blasphem/pkg/frontend" ) type Server struct { + *echo.Echo *bus.Bus - *http.Server + auth.Authenticator rootFS fs.FS wg sync.WaitGroup cfg *config.Config @@ -42,28 +46,18 @@ func logRequest(status int, r *http.Request) { } func New(cfg *config.Config) (s *Server, err error) { - - mux := http.NewServeMux() - s = &Server{ - Bus: bus.New(), - Server: &http.Server{ - Addr: cfg.Server.Bind, - Handler: mux, - }, - cfg: cfg, + Echo: echo.New(), + cfg: cfg, } + s.Echo.Debug = true + s.Echo.HideBanner = true - s.rootFS, err = fs.Sub(frontend.Root, "frontend/hass_frontend") - if err != nil { - return nil, err - } - - mux.HandleFunc("/api/websocket", s.wsHandler) - mux.Handle("/", logHandler(http.FileServer(http.FS(s.rootFS)))) - mux.HandleFunc("/auth/authorize", s.authorizeHandler) - mux.HandleFunc("/auth/providers", s.providersHandler) - mux.HandleFunc("/auth/login_flow", s.loginFlowHandler) + s.GET("/", echo.WrapHandler(frontend.FSHandler)) + s.GET("/api/websocket", s.wsHandler) + s.GET("/auth/authorize", s.AuthorizeHandler) + s.GET("/auth/providers", s.ProvidersHandler) + s.POST("/auth/login_flow", s.LoginFlowHandler) return s, nil } @@ -71,7 +65,7 @@ func New(cfg *config.Config) (s *Server, err error) { func (s *Server) Go() error { s.wg.Add(1) go func() { - err := s.ListenAndServe() + err := s.Start(s.cfg.Server.Bind) if err != nil { log.Fatal(err) } diff --git a/pkg/server/websocket.go b/pkg/server/websocket.go index 1796b42..67305d1 100644 --- a/pkg/server/websocket.go +++ b/pkg/server/websocket.go @@ -1,10 +1,11 @@ package server import ( + "errors" "log" - "net/http" "github.com/gorilla/websocket" + "github.com/labstack/echo/v4" ) var upgrader = websocket.Upgrader{ @@ -12,8 +13,8 @@ var upgrader = websocket.Upgrader{ WriteBufferSize: 1024, } -func (s *Server) wsHandler(w http.ResponseWriter, req *http.Request) { +func (s *Server) wsHandler(c echo.Context) error { log.Println("WebSocket") //conn, err := upgrader.Upgrade(w, req, nil) - panic("not implemented") + return errors.New("not handled") }