This commit is contained in:
Daniel Ponte 2022-12-20 13:16:30 -05:00
parent a468f0629b
commit 0378151b9f
12 changed files with 157 additions and 120 deletions

View file

@ -7,6 +7,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"dynatron.me/x/blasphem/internal/common" "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/cmd/serve"
"dynatron.me/x/blasphem/pkg/config" "dynatron.me/x/blasphem/pkg/config"
@ -25,7 +26,12 @@ func main() {
log.Fatal().Err(err).Msg("Config read failed") 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() err = rootCmd.Execute()
if err != nil { if err != nil {

View file

@ -9,7 +9,6 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"dynatron.me/x/blasphem/pkg/auth/provider" "dynatron.me/x/blasphem/pkg/auth/provider"
"dynatron.me/x/blasphem/pkg/frontend"
"dynatron.me/x/blasphem/pkg/storage" "dynatron.me/x/blasphem/pkg/storage"
// providers // providers
@ -42,9 +41,10 @@ type AuthError struct {
Description string `json:"error_description"` Description string `json:"error_description"`
} }
func (a *authenticator) installRoutes(e *echo.Echo) { func (a *authenticator) InstallRoutes(e *echo.Echo) {
authG := e.Group("/auth") 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.GET("/providers", a.ProvidersHandler)
authG.POST("/token", a.TokenHandler) authG.POST("/token", a.TokenHandler)
@ -55,7 +55,7 @@ func (a *authenticator) installRoutes(e *echo.Echo) {
loginFlow.DELETE("/:flow_id", a.LoginFlowDeleteHandler) 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{ a := &authenticator{
providers: make(map[string]provider.AuthProvider), providers: make(map[string]provider.AuthProvider),
} }
@ -79,8 +79,6 @@ func New(e *echo.Echo, s storage.Store) (Authenticator, error) {
return nil, err return nil, err
} }
a.installRoutes(e)
return a, nil return a, nil
} }
@ -125,5 +123,3 @@ func (a *authenticator) Check(f *LoginFlow, req *http.Request, rm map[string]int
return nil, clientID, ErrInvalidAuth return nil, clientID, ErrInvalidAuth
} }
//func (a *Authenticator) GetOrCreateCreds(

View file

@ -218,7 +218,7 @@ func (a *authenticator) NewRefreshToken(user *User, opts ...RefreshOption) (*Ref
JWTKey: generate.Hex(64), JWTKey: generate.Hex(64),
CreatedAt: &now, CreatedAt: &now,
AccessTokenExpiration: DefaultAccessExpiration, AccessTokenExpiration: DefaultAccessExpiration,
User: user, User: user,
} }
for _, opt := range opts { for _, opt := range opts {
@ -275,6 +275,11 @@ func (r *RefreshToken) AccessToken(req *http.Request) (string, error) {
}).SignedString([]byte(r.JWTKey)) }).SignedString([]byte(r.JWTKey))
} }
func (a *authenticator) ValidateAccessToken(token AccessToken) *RefreshToken {
panic("not implemented")
return nil
}
func (a *authenticator) verifyAndGetCredential(tr *TokenRequest) *Credentials { func (a *authenticator) verifyAndGetCredential(tr *TokenRequest) *Credentials {
cred, success := a.authCodes.get(tr) cred, success := a.authCodes.get(tr)
if !success { if !success {

View file

@ -2,12 +2,8 @@ package blas
import ( import (
"context" "context"
"os"
"path"
"strings"
"dynatron.me/x/blasphem/internal/common"
"dynatron.me/x/blasphem/pkg/auth" "dynatron.me/x/blasphem/pkg/auth"
"dynatron.me/x/blasphem/pkg/blas/core"
"dynatron.me/x/blasphem/pkg/bus" "dynatron.me/x/blasphem/pkg/bus"
"dynatron.me/x/blasphem/pkg/config" "dynatron.me/x/blasphem/pkg/config"
"dynatron.me/x/blasphem/pkg/storage" "dynatron.me/x/blasphem/pkg/storage"
@ -22,65 +18,12 @@ type Core interface {
Versioner Versioner
} }
var _ Core = (*core.Blas)(nil)
type Shutdowner interface { type Shutdowner interface {
Shutdown(context.Context) error ShutdownBlas(context.Context) error
} }
type Versioner interface { type Versioner interface {
Version() string 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
}

75
pkg/blas/core/core.go Normal file
View file

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

View file

@ -2,18 +2,18 @@ package serve
import ( import (
"dynatron.me/x/blasphem/internal/common" "dynatron.me/x/blasphem/internal/common"
"dynatron.me/x/blasphem/pkg/config" "dynatron.me/x/blasphem/pkg/blas"
"dynatron.me/x/blasphem/pkg/server" "dynatron.me/x/blasphem/pkg/server"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
type ServeOptions struct { type ServeOptions struct {
cfg *config.Config core blas.Core
} }
func Command(cfg *config.Config) *cobra.Command { func Command(core blas.Core) *cobra.Command {
opts := makeOptions(cfg) opts := makeOptions(core)
serveCmd := &cobra.Command{ serveCmd := &cobra.Command{
Use: "serve", Use: "serve",
Short: "starts the " + common.AppName + " server", Short: "starts the " + common.AppName + " server",
@ -23,9 +23,9 @@ func Command(cfg *config.Config) *cobra.Command {
return serveCmd return serveCmd
} }
func makeOptions(cfg *config.Config) *ServeOptions { func makeOptions(core blas.Core) *ServeOptions {
return &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 { func (o *ServeOptions) Execute() error {
server, err := server.New(o.cfg) server, err := server.New(o.core)
if err != nil { if err != nil {
return err return err
} }

View file

@ -10,6 +10,4 @@ type (
Instance interface { Instance interface {
Shutdown() Shutdown()
} }
Key string
) )

View file

@ -6,9 +6,15 @@ import (
"dynatron.me/x/blasphem/pkg/component" "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] _, already := Registry[key]
if already { if already {
panic(fmt.Sprintf("component %s already exists", key)) panic(fmt.Sprintf("component %s already exists", key))

View file

@ -5,20 +5,35 @@ import (
"io" "io"
"io/fs" "io/fs"
"net/http" "net/http"
"sync"
"dynatron.me/x/blasphem/pkg/blas"
"dynatron.me/x/blasphem/pkg/component/reg"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
const FrontendKey = "frontend"
//go:embed frontend/hass_frontend //go:embed frontend/hass_frontend
var root embed.FS 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 { return func(c echo.Context) error {
file, err := RootFS.Open(toFile) file, err := fe.rootFS.Open(toFile)
if err != nil { if err != nil {
return err 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 var err error
RootFS, err = fs.Sub(root, "frontend/hass_frontend") fe.rootFS, err = fs.Sub(root, "frontend/hass_frontend")
if err != nil { 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)
} }

View file

@ -12,46 +12,33 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/ziflex/lecho/v3" "github.com/ziflex/lecho/v3"
"dynatron.me/x/blasphem/pkg/auth"
"dynatron.me/x/blasphem/pkg/blas" "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" conf "dynatron.me/x/blasphem/pkg/server/config"
) )
type Server struct { type Server struct {
*blas.Blas blas.Core
*echo.Echo *echo.Echo
auth.Authenticator
wg sync.WaitGroup wg sync.WaitGroup
} }
func (s *Server) installRoutes() { func (s *Server) installRoutes() {
s.GET("/*", frontend.FSHandler)
s.GET("/api/websocket", s.wsHandler) s.GET("/api/websocket", s.wsHandler)
} }
func New(cfg *config.Config) (s *Server, err error) { func New(core blas.Core) (s *Server, err error) {
b, err := blas.New(cfg)
if err != nil {
return nil, err
}
s = &Server{ s = &Server{
Blas: b, Core: core,
Echo: echo.New(), Echo: echo.New(),
} }
s.Authenticator, err = auth.New(s.Echo, b.Store)
if err != nil {
return s, err
}
s.Echo.Debug = true s.Echo.Debug = true
s.Echo.HideBanner = true s.Echo.HideBanner = true
logger := lecho.From(log.Logger) logger := lecho.From(log.Logger)
s.Echo.Logger = logger s.Echo.Logger = logger
cfg := s.Conf()
if cfg.Server.LogRequestErrors { if cfg.Server.LogRequestErrors {
s.Echo.Use(lecho.Middleware(lecho.Config{ s.Echo.Use(lecho.Middleware(lecho.Config{
Logger: logger, Logger: logger,
@ -79,7 +66,7 @@ func New(cfg *config.Config) (s *Server, err error) {
} }
func (s *Server) Shutdown(ctx context.Context) error { func (s *Server) Shutdown(ctx context.Context) error {
err := s.Blas.ShutdownBlas(ctx) err := s.ShutdownBlas(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -90,8 +77,8 @@ func (s *Server) Shutdown(ctx context.Context) error {
func (s *Server) Go() error { func (s *Server) Go() error {
s.wg.Add(1) s.wg.Add(1)
go func() { go func() {
log.Info().Str("version", s.Version()).Str("bind", s.Config.Server.Bind).Msg("Server listening") log.Info().Str("version", s.Version()).Str("bind", s.Conf().Server.Bind).Msg("Server listening")
err := s.Start(s.Config.Server.Bind) err := s.Start(s.Conf().Server.Bind)
if err != nil && err != http.ErrServerClosed { if err != nil && err != http.ErrServerClosed {
s.Logger.Fatal(err) s.Logger.Fatal(err)
} }

View file

@ -19,11 +19,11 @@ type MsgBase struct {
type ( type (
wsSession struct { wsSession struct {
*websocket.Conn *websocket.Conn
b blas.Core b blas.Core
ec echo.Context ec echo.Context
h phaseHandler h phaseHandler
user *auth.User user *auth.User
refreshToken *auth.RefreshToken refreshToken *auth.RefreshToken
} }
@ -40,12 +40,11 @@ type (
} }
) )
func New(s blas.Core, c echo.Context, conn *websocket.Conn) WS { func New(s blas.Core, c echo.Context, conn *websocket.Conn) WS {
ws := &wsSession{ ws := &wsSession{
Conn: conn, Conn: conn,
b: s, b: s,
ec: c, ec: c,
} }
return ws return ws

View file

@ -14,7 +14,7 @@ type authPhase struct {
} }
func (ws *wsSession) sendAuthRequired() error { func (ws *wsSession) sendAuthRequired() error {
authReq := &struct{ authReq := &struct {
MsgBase MsgBase
Version string `json:"version"` 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") log.Error().Str("remote", ap.ec.Request().RemoteAddr).Msg("websocket auth failed")
return auth.ErrInvalidAuth return auth.ErrInvalidAuth
} }