diff --git a/cmd/blas/main.go b/cmd/blas/main.go index aa51428..749e835 100644 --- a/cmd/blas/main.go +++ b/cmd/blas/main.go @@ -7,6 +7,7 @@ import ( "github.com/rs/zerolog/log" "dynatron.me/x/blasphem/internal/common" + "dynatron.me/x/blasphem/pkg/blas/core" "dynatron.me/x/blasphem/pkg/cmd/serve" "dynatron.me/x/blasphem/pkg/config" @@ -25,7 +26,12 @@ func main() { log.Fatal().Err(err).Msg("Config read failed") } - rootCmd.AddCommand(serve.Command(config)) + bl, err := core.New(config) + if err != nil { + log.Fatal().Err(err).Msg("Core create failed") + } + + rootCmd.AddCommand(serve.Command(bl)) err = rootCmd.Execute() if err != nil { diff --git a/pkg/auth/authenticator.go b/pkg/auth/authenticator.go index 56857ba..a4893b3 100644 --- a/pkg/auth/authenticator.go +++ b/pkg/auth/authenticator.go @@ -9,7 +9,6 @@ import ( "github.com/rs/zerolog/log" "dynatron.me/x/blasphem/pkg/auth/provider" - "dynatron.me/x/blasphem/pkg/frontend" "dynatron.me/x/blasphem/pkg/storage" // providers @@ -42,9 +41,10 @@ type AuthError struct { Description string `json:"error_description"` } -func (a *authenticator) installRoutes(e *echo.Echo) { +func (a *authenticator) InstallRoutes(e *echo.Echo) { authG := e.Group("/auth") - authG.GET("/authorize", frontend.AliasHandler("authorize.html")) + panic("reinstall authorize") + //authG.GET("/authorize", frontend.AliasHandler("authorize.html")) authG.GET("/providers", a.ProvidersHandler) authG.POST("/token", a.TokenHandler) @@ -55,7 +55,7 @@ func (a *authenticator) installRoutes(e *echo.Echo) { loginFlow.DELETE("/:flow_id", a.LoginFlowDeleteHandler) } -func New(e *echo.Echo, s storage.Store) (Authenticator, error) { +func New(s storage.Store) (Authenticator, error) { a := &authenticator{ providers: make(map[string]provider.AuthProvider), } @@ -79,8 +79,6 @@ func New(e *echo.Echo, s storage.Store) (Authenticator, error) { return nil, err } - a.installRoutes(e) - return a, nil } @@ -125,5 +123,3 @@ func (a *authenticator) Check(f *LoginFlow, req *http.Request, rm map[string]int return nil, clientID, ErrInvalidAuth } - -//func (a *Authenticator) GetOrCreateCreds( diff --git a/pkg/auth/session.go b/pkg/auth/session.go index dacd327..d81e3af 100644 --- a/pkg/auth/session.go +++ b/pkg/auth/session.go @@ -218,7 +218,7 @@ func (a *authenticator) NewRefreshToken(user *User, opts ...RefreshOption) (*Ref JWTKey: generate.Hex(64), CreatedAt: &now, AccessTokenExpiration: DefaultAccessExpiration, - User: user, + User: user, } for _, opt := range opts { @@ -275,6 +275,11 @@ func (r *RefreshToken) AccessToken(req *http.Request) (string, error) { }).SignedString([]byte(r.JWTKey)) } +func (a *authenticator) ValidateAccessToken(token AccessToken) *RefreshToken { + panic("not implemented") + return nil +} + func (a *authenticator) verifyAndGetCredential(tr *TokenRequest) *Credentials { cred, success := a.authCodes.get(tr) if !success { diff --git a/pkg/blas/blas.go b/pkg/blas/blas.go index 1a11060..8193b08 100644 --- a/pkg/blas/blas.go +++ b/pkg/blas/blas.go @@ -2,12 +2,8 @@ package blas import ( "context" - "os" - "path" - "strings" - - "dynatron.me/x/blasphem/internal/common" "dynatron.me/x/blasphem/pkg/auth" + "dynatron.me/x/blasphem/pkg/blas/core" "dynatron.me/x/blasphem/pkg/bus" "dynatron.me/x/blasphem/pkg/config" "dynatron.me/x/blasphem/pkg/storage" @@ -22,65 +18,12 @@ type Core interface { Versioner } +var _ Core = (*core.Blas)(nil) + type Shutdowner interface { - Shutdown(context.Context) error + ShutdownBlas(context.Context) error } type Versioner interface { Version() string } - -type Blas struct { - bus.Bus - storage.Store - Config *config.Config -} - -func (b *Blas) Version() string { - return common.Version -} - -func (b *Blas) Conf() *config.Config { return b.Config } - -func (b *Blas) ShutdownBlas(ctx context.Context) error { - b.Bus.ShutdownBus() - b.Store.ShutdownStore() - return ctx.Err() -} - -func (b *Blas) ConfigDir() (cd string) { - if b.Config.DataDir != nil { - cd = *b.Config.DataDir - } - - home, err := os.UserHomeDir() - if err != nil { - panic(err) - } - - switch { - case cd == "": - return path.Join(home, "."+common.AppName) - case strings.HasPrefix(cd, "~/"): - return path.Join(home, cd[2:]) - default: - return cd - } -} - -func (b *Blas) openStore() error { - // TODO: based on config, open filestore or db store - stor, err := storage.OpenFileStore(b.ConfigDir()) - b.Store = stor - return err -} - -func New(cfg *config.Config) (*Blas, error) { - b := &Blas{ - Bus: bus.New(), - Config: cfg, - } - - err := b.openStore() - return b, err -} diff --git a/pkg/blas/core/core.go b/pkg/blas/core/core.go new file mode 100644 index 0000000..c26dde2 --- /dev/null +++ b/pkg/blas/core/core.go @@ -0,0 +1,75 @@ +package core + +import ( + "context" + "os" + "path" + "strings" + + "dynatron.me/x/blasphem/internal/common" + "dynatron.me/x/blasphem/pkg/auth" + "dynatron.me/x/blasphem/pkg/bus" + "dynatron.me/x/blasphem/pkg/config" + "dynatron.me/x/blasphem/pkg/storage" +) + +type Blas struct { + bus.Bus + storage.Store + auth.Authenticator + Config *config.Config +} + +func (b *Blas) Version() string { + return common.Version +} + +func (b *Blas) Conf() *config.Config { return b.Config } + +func (b *Blas) ShutdownBlas(ctx context.Context) error { + b.Bus.ShutdownBus() + b.Store.ShutdownStore() + return ctx.Err() +} + +func (b *Blas) ConfigDir() (cd string) { + if b.Config.DataDir != nil { + cd = *b.Config.DataDir + } + + home, err := os.UserHomeDir() + if err != nil { + panic(err) + } + + switch { + case cd == "": + return path.Join(home, "."+common.AppName) + case strings.HasPrefix(cd, "~/"): + return path.Join(home, cd[2:]) + default: + return cd + } +} + +func (b *Blas) openStore() error { + // TODO: based on config, open filestore or db store + stor, err := storage.OpenFileStore(b.ConfigDir()) + b.Store = stor + return err +} + +func New(cfg *config.Config) (b *Blas, err error) { + b = &Blas{ + Bus: bus.New(), + Config: cfg, + } + + err = b.openStore() + if err != nil { + return nil, err + } + + b.Authenticator, err = auth.New(b.Store) + return b, err +} diff --git a/pkg/cmd/serve/cmd.go b/pkg/cmd/serve/cmd.go index c47f40e..3908a28 100644 --- a/pkg/cmd/serve/cmd.go +++ b/pkg/cmd/serve/cmd.go @@ -2,18 +2,18 @@ package serve import ( "dynatron.me/x/blasphem/internal/common" - "dynatron.me/x/blasphem/pkg/config" + "dynatron.me/x/blasphem/pkg/blas" "dynatron.me/x/blasphem/pkg/server" "github.com/spf13/cobra" ) type ServeOptions struct { - cfg *config.Config + core blas.Core } -func Command(cfg *config.Config) *cobra.Command { - opts := makeOptions(cfg) +func Command(core blas.Core) *cobra.Command { + opts := makeOptions(core) serveCmd := &cobra.Command{ Use: "serve", Short: "starts the " + common.AppName + " server", @@ -23,9 +23,9 @@ func Command(cfg *config.Config) *cobra.Command { return serveCmd } -func makeOptions(cfg *config.Config) *ServeOptions { +func makeOptions(core blas.Core) *ServeOptions { return &ServeOptions{ - cfg: cfg, + core: core, } } @@ -34,7 +34,7 @@ func (o *ServeOptions) Options(_ *cobra.Command, args []string) error { } func (o *ServeOptions) Execute() error { - server, err := server.New(o.cfg) + server, err := server.New(o.core) if err != nil { return err } diff --git a/pkg/component/component.go b/pkg/component/component.go index a927146..ca626e3 100644 --- a/pkg/component/component.go +++ b/pkg/component/component.go @@ -10,6 +10,4 @@ type ( Instance interface { Shutdown() } - - Key string ) diff --git a/pkg/component/reg/registry.go b/pkg/component/reg/registry.go index 722cae3..489e2d7 100644 --- a/pkg/component/reg/registry.go +++ b/pkg/component/reg/registry.go @@ -6,9 +6,15 @@ import ( "dynatron.me/x/blasphem/pkg/component" ) -var Registry = make(map[component.Key]component.Setup) +type ( + Key string +) -func Register(key component.Key, c component.Setup) { +type Instance = component.Instance + +var Registry = make(map[Key]component.Setup) + +func Register(key Key, c component.Setup) { _, already := Registry[key] if already { panic(fmt.Sprintf("component %s already exists", key)) diff --git a/pkg/frontend/frontend.go b/pkg/frontend/frontend.go index 047e202..7b859b2 100644 --- a/pkg/frontend/frontend.go +++ b/pkg/frontend/frontend.go @@ -5,20 +5,35 @@ import ( "io" "io/fs" "net/http" + "sync" + + "dynatron.me/x/blasphem/pkg/blas" + "dynatron.me/x/blasphem/pkg/component/reg" "github.com/labstack/echo/v4" ) +const FrontendKey = "frontend" + //go:embed frontend/hass_frontend var root embed.FS -var RootFS fs.FS +type Frontend struct { + fsHandler echo.HandlerFunc + rootFS fs.FS -var FSHandler echo.HandlerFunc + routeInstall sync.Once +} -func AliasHandler(toFile string) echo.HandlerFunc { +func (fe *Frontend) InstallRoutes(e *echo.Echo) { + fe.routeInstall.Do(func() { + e.GET("/*", fe.fsHandler) + }) +} + +func (fe *Frontend) AliasHandler(toFile string) echo.HandlerFunc { return func(c echo.Context) error { - file, err := RootFS.Open(toFile) + file, err := fe.rootFS.Open(toFile) if err != nil { return err } @@ -33,13 +48,22 @@ func AliasHandler(toFile string) echo.HandlerFunc { } } -func init() { +func (*Frontend) Shutdown() {} + +func Setup(_ blas.Core) (reg.Instance, error) { + fe := &Frontend{} var err error - RootFS, err = fs.Sub(root, "frontend/hass_frontend") + fe.rootFS, err = fs.Sub(root, "frontend/hass_frontend") if err != nil { - panic(err) + return nil, err } - FSHandler = echo.StaticDirectoryHandler(RootFS, false) + fe.fsHandler = echo.StaticDirectoryHandler(fe.rootFS, false) + + return fe, nil +} + +func init() { + reg.Register(FrontendKey, Setup) } diff --git a/pkg/server/server.go b/pkg/server/server.go index 230adac..e6251e1 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -12,46 +12,33 @@ import ( "github.com/rs/zerolog/log" "github.com/ziflex/lecho/v3" - "dynatron.me/x/blasphem/pkg/auth" "dynatron.me/x/blasphem/pkg/blas" - "dynatron.me/x/blasphem/pkg/config" - "dynatron.me/x/blasphem/pkg/frontend" conf "dynatron.me/x/blasphem/pkg/server/config" ) type Server struct { - *blas.Blas + blas.Core *echo.Echo - auth.Authenticator wg sync.WaitGroup } func (s *Server) installRoutes() { - s.GET("/*", frontend.FSHandler) s.GET("/api/websocket", s.wsHandler) } -func New(cfg *config.Config) (s *Server, err error) { - b, err := blas.New(cfg) - if err != nil { - return nil, err - } - +func New(core blas.Core) (s *Server, err error) { s = &Server{ - Blas: b, + Core: core, Echo: echo.New(), } - s.Authenticator, err = auth.New(s.Echo, b.Store) - if err != nil { - return s, err - } - s.Echo.Debug = true s.Echo.HideBanner = true logger := lecho.From(log.Logger) s.Echo.Logger = logger + cfg := s.Conf() + if cfg.Server.LogRequestErrors { s.Echo.Use(lecho.Middleware(lecho.Config{ Logger: logger, @@ -79,7 +66,7 @@ func New(cfg *config.Config) (s *Server, err error) { } func (s *Server) Shutdown(ctx context.Context) error { - err := s.Blas.ShutdownBlas(ctx) + err := s.ShutdownBlas(ctx) if err != nil { return err } @@ -90,8 +77,8 @@ func (s *Server) Shutdown(ctx context.Context) error { func (s *Server) Go() error { s.wg.Add(1) go func() { - log.Info().Str("version", s.Version()).Str("bind", s.Config.Server.Bind).Msg("Server listening") - err := s.Start(s.Config.Server.Bind) + log.Info().Str("version", s.Version()).Str("bind", s.Conf().Server.Bind).Msg("Server listening") + err := s.Start(s.Conf().Server.Bind) if err != nil && err != http.ErrServerClosed { s.Logger.Fatal(err) } diff --git a/pkg/wsapi/api.go b/pkg/wsapi/api.go index 599f990..dff886f 100644 --- a/pkg/wsapi/api.go +++ b/pkg/wsapi/api.go @@ -19,11 +19,11 @@ type MsgBase struct { type ( wsSession struct { *websocket.Conn - b blas.Core + b blas.Core ec echo.Context - h phaseHandler + h phaseHandler - user *auth.User + user *auth.User refreshToken *auth.RefreshToken } @@ -40,12 +40,11 @@ type ( } ) - func New(s blas.Core, c echo.Context, conn *websocket.Conn) WS { ws := &wsSession{ Conn: conn, - b: s, - ec: c, + b: s, + ec: c, } return ws diff --git a/pkg/wsapi/auth.go b/pkg/wsapi/auth.go index 6804d15..5ab93ca 100644 --- a/pkg/wsapi/auth.go +++ b/pkg/wsapi/auth.go @@ -14,7 +14,7 @@ type authPhase struct { } func (ws *wsSession) sendAuthRequired() error { - authReq := &struct{ + authReq := &struct { MsgBase Version string `json:"version"` }{ @@ -53,7 +53,5 @@ func (ap *authPhase) handleMsg(r io.Reader) error { log.Error().Str("remote", ap.ec.Request().RemoteAddr).Msg("websocket auth failed") - return auth.ErrInvalidAuth } -