diff --git a/pkg/auth/authenticator.go b/pkg/auth/authenticator.go index ed4c236..eaf01f4 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/components" "dynatron.me/x/blasphem/pkg/storage" // providers @@ -35,7 +34,7 @@ type authenticator struct { type Authenticator interface { ValidateAccessToken(token AccessToken) *RefreshToken - InstallRoutes(e *echo.Echo, comp components.Componenter) + InstallRoutes(e *echo.Echo) } type AuthError struct { @@ -43,7 +42,7 @@ type AuthError struct { Description string `json:"error_description"` } -func (a *authenticator) InstallRoutes(e *echo.Echo, comp components.Componenter) { +func (a *authenticator) InstallRoutes(e *echo.Echo) { authG := e.Group("/auth") authG.GET("/providers", a.ProvidersHandler) authG.POST("/token", a.TokenHandler) diff --git a/pkg/blas/blas.go b/pkg/blas/blas.go index 9fdc490..78b4823 100644 --- a/pkg/blas/blas.go +++ b/pkg/blas/blas.go @@ -8,6 +8,7 @@ import ( "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/components" "dynatron.me/x/blasphem/pkg/config" @@ -21,6 +22,7 @@ type Blas struct { storage.Store auth.Authenticator Config *config.Config + core.WebSocketManager components components.ComponentStore } diff --git a/pkg/blas/components.go b/pkg/blas/components.go index 81b110c..0fa4d58 100644 --- a/pkg/blas/components.go +++ b/pkg/blas/components.go @@ -7,7 +7,7 @@ import ( "dynatron.me/x/blasphem/pkg/components" ) -type Setup func(core.Core) (components.Component, error) +type Setup func(core.Blas) (components.Component, error) var Registry = make(map[components.ComponentKey]Setup) diff --git a/pkg/blas/core/core.go b/pkg/blas/core/core.go index a7ee257..fbafac7 100644 --- a/pkg/blas/core/core.go +++ b/pkg/blas/core/core.go @@ -10,16 +10,35 @@ import ( "dynatron.me/x/blasphem/pkg/storage" ) -type Core interface { +type Blas interface { auth.Authenticator bus.Bus storage.Store config.Configured + components.Componenter + + WebSocketManager + Shutdowner Versioner - components.Componenter } +type WebSocketManager interface { + // Register registers a websocket command. + // cmd is the first part, before first slash + // dataNew is a function to create a new message datatype + RegisterWSCommand(cmd string, hnd Handler, dataNew NewData) + WSCommandHandler(cmd string) (NewData, Handler, error) +} + +type WebSocketSession interface { + Go() error + Blas() Blas +} + +type Handler func(wss WebSocketSession, msg interface{}) error +type NewData func() interface{} + type Shutdowner interface { ShutdownBlas(context.Context) error } diff --git a/pkg/cmd/serve/cmd.go b/pkg/cmd/serve/cmd.go index 02773bd..eb1eb55 100644 --- a/pkg/cmd/serve/cmd.go +++ b/pkg/cmd/serve/cmd.go @@ -9,10 +9,10 @@ import ( ) type ServeOptions struct { - core blas.Core + core blas.Blas } -func Command(core blas.Core) *cobra.Command { +func Command(core blas.Blas) *cobra.Command { opts := makeOptions(core) serveCmd := &cobra.Command{ Use: "serve", @@ -23,7 +23,7 @@ func Command(core blas.Core) *cobra.Command { return serveCmd } -func makeOptions(core blas.Core) *ServeOptions { +func makeOptions(core blas.Blas) *ServeOptions { return &ServeOptions{ core: core, } diff --git a/pkg/frontend/frontend.go b/pkg/frontend/frontend.go index 84f6a68..9d02411 100644 --- a/pkg/frontend/frontend.go +++ b/pkg/frontend/frontend.go @@ -52,7 +52,7 @@ func (fe *Frontend) AliasHandler(toFile string) echo.HandlerFunc { func (*Frontend) Shutdown() {} -func Setup(_ core.Core) (components.Component, error) { +func Setup(_ core.Blas) (components.Component, error) { fe := &Frontend{} var err error diff --git a/pkg/server/server.go b/pkg/server/server.go index df52989..4283051 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -19,7 +19,7 @@ import ( ) type Server struct { - core.Core + core.Blas *echo.Echo wg sync.WaitGroup } @@ -32,7 +32,7 @@ func (s *Server) installRoutes() { s.GET("/api/websocket", s.wsHandler) s.Component(frontend.FrontendKey).(RouteHaver).InstallRoutes(s.Echo) - s.Core.(*blas.Blas).Authenticator.InstallRoutes(s.Echo, s.Core) + s.Blas.(*blas.Blas).Authenticator.InstallRoutes(s.Echo) for _, c := range s.Components() { if rh, ok := c.(RouteHaver); ok { @@ -41,9 +41,9 @@ func (s *Server) installRoutes() { } } -func New(core core.Core) (s *Server, err error) { +func New(core core.Blas) (s *Server, err error) { s = &Server{ - Core: core, + Blas: core, Echo: echo.New(), } diff --git a/pkg/server/websocket.go b/pkg/server/websocket.go index 0534361..dee373a 100644 --- a/pkg/server/websocket.go +++ b/pkg/server/websocket.go @@ -23,5 +23,5 @@ func (s *Server) wsHandler(c echo.Context) error { log.Debug().Str("remote", c.Request().RemoteAddr).Msg("WS") - return wsapi.New(s, c, conn).Handle() + return wsapi.NewSession(s, c, conn).Go() } diff --git a/pkg/wsapi/api.go b/pkg/wsapi/api.go index bccd4dc..5b6d4fd 100644 --- a/pkg/wsapi/api.go +++ b/pkg/wsapi/api.go @@ -1,16 +1,22 @@ package wsapi import ( + "encoding/json" + "errors" "io" "dynatron.me/x/blasphem/pkg/auth" - blas "dynatron.me/x/blasphem/pkg/blas/core" + "dynatron.me/x/blasphem/pkg/blas/core" "github.com/gorilla/websocket" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" ) +var ( + NoSuchHandlerErr = errors.New("bad websocket command") +) + type Type string type MsgBase struct { Type Type `json:"type"` @@ -19,7 +25,7 @@ type MsgBase struct { type ( wsSession struct { *websocket.Conn - b blas.Core + b core.Blas ec echo.Context h phaseHandler @@ -27,10 +33,6 @@ type ( refreshToken *auth.RefreshToken } - WS interface { - Handle() error - } - phaseHandler interface { handleMsg(io.Reader) error } @@ -38,19 +40,56 @@ type ( cmdHandler struct { *wsSession } + + wsEntry struct { + dataNew core.NewData + hnd core.Handler + } + + wsRegistry map[string]wsEntry + + wsManager struct { + r wsRegistry + } ) -func New(s blas.Core, c echo.Context, conn *websocket.Conn) WS { +func (wsm *wsManager) RegisterWSCommand(cmd string, hnd core.Handler, dataNew core.NewData) { + wsm.r[cmd] = wsEntry{ + dataNew: dataNew, + hnd: hnd, + } +} + +func (wsm *wsManager) WSCommandHandler(cmd string) (core.NewData, core.Handler, error) { + wse, ok := wsm.r[cmd] + if !ok { + return nil, nil, NoSuchHandlerErr + } + + return wse.dataNew, wse.hnd, nil +} + +func NewManager() core.WebSocketManager { + return &wsManager{ + r: make(wsRegistry), + } +} + +func NewSession(s core.Blas, c echo.Context, conn *websocket.Conn) core.WebSocketSession { ws := &wsSession{ Conn: conn, b: s, ec: c, } + ws.h = &authPhase{ws} + return ws } -func (ws *wsSession) Handle() error { +func (ws *wsSession) Blas() core.Blas { return ws.b } + +func (ws *wsSession) Go() error { err := ws.sendAuthRequired() if err != nil { return err @@ -76,5 +115,22 @@ type cmdMsg struct { type MsgType string func (ws *cmdHandler) handleMsg(r io.Reader) error { - return nil + var msgMap map[string]interface{} + err := json.NewDecoder(r).Decode(&msgMap) + if err != nil { + return err + } + + msgType, ok := msgMap["type"].(string) + if !ok { + return NoSuchHandlerErr + } + + newData, hand, err := ws.b.WSCommandHandler(msgType) + if !ok { + return err + } + + nd := newData() + return hand(ws, nd) } diff --git a/pkg/wsapi/auth.go b/pkg/wsapi/auth.go index 5ab93ca..9695a74 100644 --- a/pkg/wsapi/auth.go +++ b/pkg/wsapi/auth.go @@ -37,9 +37,18 @@ func (ap *authPhase) finishAuth(rt *auth.RefreshToken) { ap.user = rt.User ap.refreshToken = rt ap.h = &cmdHandler{ap.wsSession} + ap.sendAuthOK() +} + +func (ap *authPhase) sendAuthOK() error { + return ap.WriteJSON(struct { + Type string `json:"type"` + Version string `json:"version"` + }{Type: "auth_ok", Version: ap.Blas().Version()}) } func (ap *authPhase) handleMsg(r io.Reader) error { + log.Debug().Interface("ap", ap).Msg("auth handlemsg") var authMsg authMsg err := json.NewDecoder(r).Decode(&authMsg) if err != nil { @@ -49,6 +58,7 @@ func (ap *authPhase) handleMsg(r io.Reader) error { refreshToken := ap.b.ValidateAccessToken(authMsg.AccessToken) if refreshToken != nil { ap.finishAuth(refreshToken) + return ap.sendAuthOK() } log.Error().Str("remote", ap.ec.Request().RemoteAddr).Msg("websocket auth failed")