package app import ( "context" "net/http" "sync" "time" "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" appSpec "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" edgeHTTP "forge.cadoles.com/arcad/edge/pkg/http" authHTTP "forge.cadoles.com/arcad/edge/pkg/module/auth/http" "gitlab.com/wpetit/goweb/logger" "forge.cadoles.com/arcad/edge/pkg/bundle" "github.com/go-chi/chi/middleware" "github.com/go-chi/chi/v5" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/pkg/errors" _ "forge.cadoles.com/Cadoles/emissary/internal/imports/passwd" ) const defaultCookieDuration time.Duration = 24 * time.Hour type Server struct { bundle bundle.Bundle handlerOptions []edgeHTTP.HandlerOptionFunc server *http.Server serverMutex sync.RWMutex auth *appSpec.Auth } func (s *Server) Start(ctx context.Context, addr string) (err error) { if s.server != nil { if err := s.Stop(); err != nil { return errors.WithStack(err) } } router := chi.NewRouter() router.Use(middleware.Logger) handler := edgeHTTP.NewHandler(s.handlerOptions...) if err := handler.Load(s.bundle); err != nil { return errors.Wrap(err, "could not load app bundle") } if err := s.configureAuth(router, s.auth); err != nil { return errors.WithStack(err) } router.Handle("/*", handler) server := &http.Server{ Addr: addr, Handler: router, } go func() { defer func() { if recovered := recover(); recovered != nil { if err, ok := recovered.(error); ok { logger.Error(ctx, err.Error(), logger.E(errors.WithStack(err))) return } panic(recovered) } }() defer func() { if err := s.Stop(); err != nil { panic(errors.WithStack(err)) } }() if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { panic(errors.WithStack(err)) } }() s.serverMutex.Lock() s.server = server s.serverMutex.Unlock() return nil } func (s *Server) Running() bool { s.serverMutex.RLock() defer s.serverMutex.RUnlock() return s.server != nil } func (s *Server) Stop() error { if s.server == nil { return nil } defer func() { s.serverMutex.Lock() s.server = nil s.serverMutex.Unlock() }() if err := s.server.Close(); err != nil { panic(errors.WithStack(err)) } return nil } func (s *Server) configureAuth(router chi.Router, auth *spec.Auth) error { if auth == nil { return nil } switch { case auth.Local != nil: var rawKey any = s.auth.Local.Key if strKey, ok := rawKey.(string); ok { rawKey = []byte(strKey) } key, err := jwk.FromRaw(rawKey) if err != nil { return errors.WithStack(err) } cookieDuration := defaultCookieDuration if s.auth.Local.CookieDuration != "" { cookieDuration, err = time.ParseDuration(s.auth.Local.CookieDuration) if err != nil { return errors.WithStack(err) } } router.Handle("/auth/*", authHTTP.NewLocalHandler( jwa.HS256, key, authHTTP.WithRoutePrefix("/auth"), authHTTP.WithAccounts(s.auth.Local.Accounts...), authHTTP.WithCookieOptions(s.auth.Local.CookieDomain, cookieDuration), )) } return nil } func NewServer(bundle bundle.Bundle, auth *appSpec.Auth, handlerOptions ...edgeHTTP.HandlerOptionFunc) *Server { return &Server{ bundle: bundle, auth: auth, handlerOptions: handlerOptions, } }