package app import ( "bytes" "context" "database/sql" "path/filepath" "sync" "text/template" "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" appSpec "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" "forge.cadoles.com/Cadoles/emissary/internal/jwk" "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" appModule "forge.cadoles.com/arcad/edge/pkg/module/app" "forge.cadoles.com/arcad/edge/pkg/module/auth" "forge.cadoles.com/arcad/edge/pkg/module/blob" "forge.cadoles.com/arcad/edge/pkg/module/cast" "forge.cadoles.com/arcad/edge/pkg/module/net" "forge.cadoles.com/arcad/edge/pkg/storage/sqlite" "github.com/Masterminds/sprig/v3" "github.com/dop251/goja" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/pkg/errors" ) func (c *Controller) getHandlerOptions(ctx context.Context, appKey string, specs *spec.Spec) ([]edgeHTTP.HandlerOptionFunc, error) { dataDir, err := c.ensureAppDataDir(ctx, appKey) if err != nil { return nil, errors.Wrap(err, "could not retrieve app data dir") } dbFile := filepath.Join(dataDir, appKey+".sqlite") db, err := sqlite.Open(dbFile) if err != nil { return nil, errors.Wrapf(err, "could not open database file '%s'", dbFile) } keySet, err := getAuthKeySet(specs.Config) if err != nil { return nil, errors.Wrap(err, "could not retrieve auth key set") } bundles := make([]string, 0, len(specs.Apps)) for appKey, app := range specs.Apps { path := c.getAppBundlePath(appKey, app.Format) bundles = append(bundles, path) } getAppURL := createGetAppURL(specs) bus := memory.NewBus() modules := getAppModules(bus, db, specs, keySet, getAppURL, bundles) options := []edgeHTTP.HandlerOptionFunc{ edgeHTTP.WithBus(bus), edgeHTTP.WithServerModules(modules...), } return options, nil } func getAuthKeySet(config *spec.Config) (jwk.Set, error) { keySet := jwk.NewSet() if config == nil { return nil, nil } auth := config.Auth if auth == nil { return nil, nil } switch { case auth.Local != nil: var ( key jwk.Key err error ) switch typedKey := auth.Local.Key.(type) { case string: key, err = jwk.FromRaw([]byte(typedKey)) if err != nil { return nil, errors.Wrap(err, "could not parse local auth key") } if err := key.Set(jwk.AlgorithmKey, jwa.HS256); err != nil { return nil, errors.WithStack(err) } default: return nil, errors.Errorf("unexpected key type '%T'", auth.Local.Key) } if err := keySet.AddKey(key); err != nil { return nil, errors.WithStack(err) } } return keySet, nil } func createGetAppURL(specs *spec.Spec) GetURLFunc { var ( compileOnce sync.Once urlTemplate *template.Template err error ) return func(ctx context.Context, manifest *app.Manifest) (string, error) { if err != nil { return "", errors.WithStack(err) } var appURLTemplate string if specs.Config == nil || specs.Config.AppURLTemplate == "" { appURLTemplate = `http://{{ last ( splitList "." ( toString .Manifest.ID ) ) }}.local` } else { appURLTemplate = specs.Config.AppURLTemplate } compileOnce.Do(func() { urlTemplate, err = template.New("").Funcs(sprig.TxtFuncMap()).Parse(appURLTemplate) }) var buf bytes.Buffer data := struct { Manifest *app.Manifest Specs *spec.Spec }{ Manifest: manifest, Specs: specs, } if err := urlTemplate.Execute(&buf, data); err != nil { return "", errors.WithStack(err) } return buf.String(), nil } } func getAppModules(bus bus.Bus, db *sql.DB, spec *appSpec.Spec, keySet jwk.Set, getAppURL GetURLFunc, bundles []string) []app.ServerModuleFactory { ds := sqlite.NewDocumentStoreWithDB(db) bs := sqlite.NewBlobStoreWithDB(db) return []app.ServerModuleFactory{ module.ContextModuleFactory(), module.ConsoleModuleFactory(), cast.CastModuleFactory(), module.LifecycleModuleFactory(), net.ModuleFactory(bus), module.RPCModuleFactory(bus), module.StoreModuleFactory(ds), blob.ModuleFactory(bus, bs), module.Extends( auth.ModuleFactory( auth.WithJWT(func() (jwk.Set, error) { return keySet, nil }), ), 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")) } }, ), appModule.ModuleFactory(NewAppRepository(getAppURL, bundles...)), } }