207 lines
4.7 KiB
Go
207 lines
4.7 KiB
Go
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_TENANT", "arcad_tenant"); err != nil {
|
|
panic(errors.New("could not set 'CLAIM_TENANT' property"))
|
|
}
|
|
|
|
if err := o.Set("CLAIM_ENTRYPOINT", "arcad_entrypoint"); err != nil {
|
|
panic(errors.New("could not set 'CLAIM_ENTRYPOINT' property"))
|
|
}
|
|
|
|
if err := o.Set("CLAIM_ROLE", "arcad_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,
|
|
}
|
|
}
|