package app import ( "context" "database/sql" "net/http" "sync" appSpec "forge.cadoles.com/Cadoles/emissary/internal/spec/app" "forge.cadoles.com/arcad/edge/pkg/app" "forge.cadoles.com/arcad/edge/pkg/bus" "forge.cadoles.com/arcad/edge/pkg/bus/memory" edgeHTTP "forge.cadoles.com/arcad/edge/pkg/http" "forge.cadoles.com/arcad/edge/pkg/module" "forge.cadoles.com/arcad/edge/pkg/module/auth" authHTTP "forge.cadoles.com/arcad/edge/pkg/module/auth/http" "forge.cadoles.com/arcad/edge/pkg/module/cast" "forge.cadoles.com/arcad/edge/pkg/module/net" "forge.cadoles.com/arcad/edge/pkg/storage" "forge.cadoles.com/arcad/edge/pkg/storage/sqlite" "gitlab.com/wpetit/goweb/logger" "forge.cadoles.com/arcad/edge/pkg/bundle" "github.com/dop251/goja" "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/arcad/edge/pkg/module/auth/http/passwd/argon2id" _ "forge.cadoles.com/arcad/edge/pkg/module/auth/http/passwd/plain" ) type Server struct { bundle bundle.Bundle db *sql.DB server *http.Server serverMutex sync.RWMutex auth *appSpec.Auth keySet jwk.Set } 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) bus := memory.NewBus() ds := sqlite.NewDocumentStoreWithDB(s.db) bs := sqlite.NewBlobStoreWithDB(s.db) handler := edgeHTTP.NewHandler( edgeHTTP.WithBus(bus), edgeHTTP.WithServerModules(s.getAppModules(bus, ds, bs)...), ) if err := handler.Load(s.bundle); err != nil { return errors.Wrap(err, "could not load app bundle") } if s.auth != nil { if s.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) } if err := key.Set(jwk.AlgorithmKey, jwa.HS256); err != nil { return errors.WithStack(err) } keySet := jwk.NewSet() if err := keySet.AddKey(key); err != nil { return errors.WithStack(err) } s.keySet = keySet router.Handle("/auth/*", authHTTP.NewLocalHandler( jwa.HS256, key, authHTTP.WithRoutePrefix("/auth"), authHTTP.WithAccounts(s.auth.Local.Accounts...), )) } } 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) getAppModules(bus bus.Bus, ds storage.DocumentStore, bs storage.BlobStore) []app.ServerModuleFactory { return []app.ServerModuleFactory{ module.ContextModuleFactory(), module.ConsoleModuleFactory(), cast.CastModuleFactory(), module.LifecycleModuleFactory(), net.ModuleFactory(bus), module.RPCModuleFactory(bus), module.StoreModuleFactory(ds), module.BlobModuleFactory(bus, bs), module.Extends( auth.ModuleFactory( auth.WithJWT(s.getJWTKeySet), ), func(o *goja.Object) { if err := o.Set("CLAIM_ROLE", "role"); err != nil { panic(errors.New("could not set 'CLAIM_ROLE' property")) } if err := o.Set("CLAIM_PREFERRED_USERNAME", "preferred_username"); err != nil { panic(errors.New("could not set 'CLAIM_PREFERRED_USERNAME' property")) } }, ), } } func (s *Server) getJWTKeySet() (jwk.Set, error) { return s.keySet, nil } func NewServer(bundle bundle.Bundle, db *sql.DB, auth *appSpec.Auth) *Server { return &Server{ bundle: bundle, db: db, auth: auth, } }