From 5cad1f02198636919d61563ce8a9eef2db129563 Mon Sep 17 00:00:00 2001 From: Stavros Date: Sat, 13 Dec 2025 15:58:41 +0200 Subject: [PATCH] refactor: split bootstrap to smaller files for better readability --- internal/bootstrap/app_bootstrap.go | 236 +++++------------------- internal/bootstrap/routes_boostrap.go | 105 +++++++++++ internal/bootstrap/service_bootstrap.go | 100 ++++++++++ 3 files changed, 250 insertions(+), 191 deletions(-) create mode 100644 internal/bootstrap/routes_boostrap.go create mode 100644 internal/bootstrap/service_bootstrap.go diff --git a/internal/bootstrap/app_bootstrap.go b/internal/bootstrap/app_bootstrap.go index 01b60f6..ff7f974 100644 --- a/internal/bootstrap/app_bootstrap.go +++ b/internal/bootstrap/app_bootstrap.go @@ -13,32 +13,26 @@ import ( "time" "tinyauth/internal/config" "tinyauth/internal/controller" - "tinyauth/internal/middleware" "tinyauth/internal/model" - "tinyauth/internal/service" "tinyauth/internal/utils" - "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" "gorm.io/gorm" ) -type Controller interface { - SetupRoutes() -} - -type Middleware interface { - Middleware() gin.HandlerFunc - Init() error -} - -type Service interface { - Init() error -} - type BootstrapApp struct { - config config.Config - uuid string + config config.Config + context struct { + uuid string + cookieDomain string + sessionCookieName string + csrfCookieName string + redirectCookieName string + users []config.User + oauthProviders map[string]config.OAuthServiceConfig + configuredProviders []controller.Provider + } + services Services } func NewBootstrapApp(config config.Config) *BootstrapApp { @@ -55,6 +49,8 @@ func (app *BootstrapApp) Setup() error { return err } + app.context.users = users + // Get OAuth configs oauthProviders, err := utils.GetOAuthProvidersConfig(os.Environ(), os.Args, app.config.AppURL) @@ -62,6 +58,8 @@ func (app *BootstrapApp) Setup() error { return err } + app.context.oauthProviders = oauthProviders + // Get cookie domain cookieDomain, err := utils.GetCookieDomain(app.config.AppURL) @@ -69,97 +67,33 @@ func (app *BootstrapApp) Setup() error { return err } + app.context.cookieDomain = cookieDomain + // Cookie names appUrl, _ := url.Parse(app.config.AppURL) // Already validated - uuid := utils.GenerateUUID(appUrl.Hostname()) - app.uuid = uuid - cookieId := strings.Split(uuid, "-")[0] - sessionCookieName := fmt.Sprintf("%s-%s", config.SessionCookieName, cookieId) - csrfCookieName := fmt.Sprintf("%s-%s", config.CSRFCookieName, cookieId) - redirectCookieName := fmt.Sprintf("%s-%s", config.RedirectCookieName, cookieId) + app.context.uuid = utils.GenerateUUID(appUrl.Hostname()) + cookieId := strings.Split(app.context.uuid, "-")[0] + app.context.sessionCookieName = fmt.Sprintf("%s-%s", config.SessionCookieName, cookieId) + app.context.csrfCookieName = fmt.Sprintf("%s-%s", config.CSRFCookieName, cookieId) + app.context.redirectCookieName = fmt.Sprintf("%s-%s", config.RedirectCookieName, cookieId) // Dumps log.Trace().Interface("config", app.config).Msg("Config dump") - log.Trace().Interface("users", users).Msg("Users dump") - log.Trace().Interface("oauthProviders", oauthProviders).Msg("OAuth providers dump") - log.Trace().Str("cookieDomain", cookieDomain).Msg("Cookie domain") - log.Trace().Str("sessionCookieName", sessionCookieName).Msg("Session cookie name") - log.Trace().Str("csrfCookieName", csrfCookieName).Msg("CSRF cookie name") - log.Trace().Str("redirectCookieName", redirectCookieName).Msg("Redirect cookie name") + log.Trace().Interface("users", app.context.users).Msg("Users dump") + log.Trace().Interface("oauthProviders", app.context.oauthProviders).Msg("OAuth providers dump") + log.Trace().Str("cookieDomain", app.context.cookieDomain).Msg("Cookie domain") + log.Trace().Str("sessionCookieName", app.context.sessionCookieName).Msg("Session cookie name") + log.Trace().Str("csrfCookieName", app.context.csrfCookieName).Msg("CSRF cookie name") + log.Trace().Str("redirectCookieName", app.context.redirectCookieName).Msg("Redirect cookie name") - // Create configs - authConfig := service.AuthServiceConfig{ - Users: users, - OauthWhitelist: app.config.OAuthWhitelist, - SessionExpiry: app.config.SessionExpiry, - SecureCookie: app.config.SecureCookie, - CookieDomain: cookieDomain, - LoginTimeout: app.config.LoginTimeout, - LoginMaxRetries: app.config.LoginMaxRetries, - SessionCookieName: sessionCookieName, - } - - // Setup services - var ldapService *service.LdapService - - if app.config.LdapAddress != "" { - ldapConfig := service.LdapServiceConfig{ - Address: app.config.LdapAddress, - BindDN: app.config.LdapBindDN, - BindPassword: app.config.LdapBindPassword, - BaseDN: app.config.LdapBaseDN, - Insecure: app.config.LdapInsecure, - SearchFilter: app.config.LdapSearchFilter, - } - - ldapService = service.NewLdapService(ldapConfig) - - err := ldapService.Init() - - if err != nil { - log.Warn().Err(err).Msg("Failed to initialize LDAP service, continuing without LDAP") - ldapService = nil - } - } - - // Bootstrap database - databaseService := service.NewDatabaseService(service.DatabaseServiceConfig{ - DatabasePath: app.config.DatabasePath, - }) - - log.Debug().Str("service", fmt.Sprintf("%T", databaseService)).Msg("Initializing service") - - err = databaseService.Init() + // Services + services, err := app.initServices() if err != nil { - return fmt.Errorf("failed to initialize database service: %w", err) + return fmt.Errorf("failed to initialize services: %w", err) } - database := databaseService.GetDatabase() - - // Create services - dockerService := service.NewDockerService() - aclsService := service.NewAccessControlsService(dockerService) - authService := service.NewAuthService(authConfig, dockerService, ldapService, database) - oauthBrokerService := service.NewOAuthBrokerService(oauthProviders) - - // Initialize services (order matters) - services := []Service{ - dockerService, - aclsService, - authService, - oauthBrokerService, - } - - for _, svc := range services { - if svc != nil { - log.Debug().Str("service", fmt.Sprintf("%T", svc)).Msg("Initializing service") - err := svc.Init() - if err != nil { - return err - } - } - } + app.services = services // Configured providers configuredProviders := make([]controller.Provider, 0) @@ -176,7 +110,7 @@ func (app *BootstrapApp) Setup() error { return configuredProviders[i].Name < configuredProviders[j].Name }) - if authService.UserAuthConfigured() || ldapService != nil { + if services.authService.UserAuthConfigured() { configuredProviders = append(configuredProviders, controller.Provider{ Name: "Username", ID: "username", @@ -190,92 +124,16 @@ func (app *BootstrapApp) Setup() error { return fmt.Errorf("no authentication providers configured") } - // Create engine - engine := gin.New() - engine.Use(gin.Recovery()) - - if len(app.config.TrustedProxies) > 0 { - err := engine.SetTrustedProxies(strings.Split(app.config.TrustedProxies, ",")) - - if err != nil { - return fmt.Errorf("failed to set trusted proxies: %w", err) - } - } - - // Create middlewares - var middlewares []Middleware - - contextMiddleware := middleware.NewContextMiddleware(middleware.ContextMiddlewareConfig{ - CookieDomain: cookieDomain, - }, authService, oauthBrokerService) - - uiMiddleware := middleware.NewUIMiddleware() - zerologMiddleware := middleware.NewZerologMiddleware() - - middlewares = append(middlewares, contextMiddleware, uiMiddleware, zerologMiddleware) - - for _, middleware := range middlewares { - log.Debug().Str("middleware", fmt.Sprintf("%T", middleware)).Msg("Initializing middleware") - err := middleware.Init() - if err != nil { - return fmt.Errorf("failed to initialize middleware %T: %w", middleware, err) - } - engine.Use(middleware.Middleware()) - } - - // Create routers - mainRouter := engine.Group("") - apiRouter := engine.Group("/api") - - // Create controllers - contextController := controller.NewContextController(controller.ContextControllerConfig{ - Providers: configuredProviders, - Title: app.config.Title, - AppURL: app.config.AppURL, - CookieDomain: cookieDomain, - ForgotPasswordMessage: app.config.ForgotPasswordMessage, - BackgroundImage: app.config.BackgroundImage, - OAuthAutoRedirect: app.config.OAuthAutoRedirect, - DisableUIWarnings: app.config.DisableUIWarnings, - }, apiRouter) - - oauthController := controller.NewOAuthController(controller.OAuthControllerConfig{ - AppURL: app.config.AppURL, - SecureCookie: app.config.SecureCookie, - CSRFCookieName: csrfCookieName, - RedirectCookieName: redirectCookieName, - CookieDomain: cookieDomain, - }, apiRouter, authService, oauthBrokerService) - - proxyController := controller.NewProxyController(controller.ProxyControllerConfig{ - AppURL: app.config.AppURL, - }, apiRouter, aclsService, authService) - - userController := controller.NewUserController(controller.UserControllerConfig{ - CookieDomain: cookieDomain, - }, apiRouter, authService) - - resourcesController := controller.NewResourcesController(controller.ResourcesControllerConfig{ - ResourcesDir: app.config.ResourcesDir, - ResourcesDisabled: app.config.DisableResources, - }, mainRouter) - - healthController := controller.NewHealthController(apiRouter) - // Setup routes - controller := []Controller{ - contextController, - oauthController, - proxyController, - userController, - healthController, - resourcesController, + engine, err := app.setupRoutes() + + if err != nil { + return fmt.Errorf("failed to setup routes: %w", err) } - for _, ctrl := range controller { - log.Debug().Msgf("Setting up %T controller", ctrl) - ctrl.SetupRoutes() - } + // Start DB cleanup routine + log.Debug().Msg("Starting database cleanup routine") + go app.dbCleanup(services.databaseService.GetDatabase()) // If analytics are not disabled, start heartbeat if !app.config.DisableAnalytics { @@ -283,13 +141,8 @@ func (app *BootstrapApp) Setup() error { go app.heartbeat() } - // Start DB cleanup routine - log.Debug().Msg("Starting database cleanup routine") - go app.dbCleanup(database) - // If we have an socket path, bind to it if app.config.SocketPath != "" { - // Remove existing socket file if _, err := os.Stat(app.config.SocketPath); err == nil { log.Info().Msgf("Removing existing socket file %s", app.config.SocketPath) err := os.Remove(app.config.SocketPath) @@ -298,7 +151,6 @@ func (app *BootstrapApp) Setup() error { } } - // Start server with unix socket log.Info().Msgf("Starting server on unix socket %s", app.config.SocketPath) if err := engine.RunUnix(app.config.SocketPath); err != nil { log.Fatal().Err(err).Msg("Failed to start server") @@ -328,7 +180,7 @@ func (app *BootstrapApp) heartbeat() { var body heartbeat - body.UUID = app.uuid + body.UUID = app.context.uuid body.Version = config.Version bodyJson, err := json.Marshal(body) @@ -338,7 +190,9 @@ func (app *BootstrapApp) heartbeat() { return } - client := &http.Client{} + client := &http.Client{ + Timeout: time.Duration(10) * time.Second, // The server should never take more than 10 seconds to respond + } heartbeatURL := config.ApiServer + "/v1/instances/heartbeat" diff --git a/internal/bootstrap/routes_boostrap.go b/internal/bootstrap/routes_boostrap.go new file mode 100644 index 0000000..dc99886 --- /dev/null +++ b/internal/bootstrap/routes_boostrap.go @@ -0,0 +1,105 @@ +package bootstrap + +import ( + "fmt" + "strings" + "tinyauth/internal/controller" + "tinyauth/internal/middleware" + + "github.com/gin-gonic/gin" +) + +func (app *BootstrapApp) setupRoutes() (*gin.Engine, error) { + engine := gin.New() + engine.Use(gin.Recovery()) + + if len(app.config.TrustedProxies) > 0 { + err := engine.SetTrustedProxies(strings.Split(app.config.TrustedProxies, ",")) + + if err != nil { + return nil, fmt.Errorf("failed to set trusted proxies: %w", err) + } + } + + contextMiddleware := middleware.NewContextMiddleware(middleware.ContextMiddlewareConfig{ + CookieDomain: app.context.cookieDomain, + }, app.services.authService, app.services.oauthBrokerService) + + err := contextMiddleware.Init() + + if err != nil { + return nil, fmt.Errorf("failed to initialize context middleware: %w", err) + } + + engine.Use(contextMiddleware.Middleware()) + + uiMiddleware := middleware.NewUIMiddleware() + + err = uiMiddleware.Init() + + if err != nil { + return nil, fmt.Errorf("failed to initialize UI middleware: %w", err) + } + + engine.Use(uiMiddleware.Middleware()) + + zerologMiddleware := middleware.NewZerologMiddleware() + + err = zerologMiddleware.Init() + + if err != nil { + return nil, fmt.Errorf("failed to initialize zerolog middleware: %w", err) + } + + engine.Use(zerologMiddleware.Middleware()) + + apiRouter := engine.Group("/api") + + contextController := controller.NewContextController(controller.ContextControllerConfig{ + Providers: app.context.configuredProviders, + Title: app.config.Title, + AppURL: app.config.AppURL, + CookieDomain: app.context.cookieDomain, + ForgotPasswordMessage: app.config.ForgotPasswordMessage, + BackgroundImage: app.config.BackgroundImage, + OAuthAutoRedirect: app.config.OAuthAutoRedirect, + DisableUIWarnings: app.config.DisableUIWarnings, + }, apiRouter) + + contextController.SetupRoutes() + + oauthController := controller.NewOAuthController(controller.OAuthControllerConfig{ + AppURL: app.config.AppURL, + SecureCookie: app.config.SecureCookie, + CSRFCookieName: app.context.csrfCookieName, + RedirectCookieName: app.context.redirectCookieName, + CookieDomain: app.context.cookieDomain, + }, apiRouter, app.services.authService, app.services.oauthBrokerService) + + oauthController.SetupRoutes() + + proxyController := controller.NewProxyController(controller.ProxyControllerConfig{ + AppURL: app.config.AppURL, + }, apiRouter, app.services.accessControlService, app.services.authService) + + proxyController.SetupRoutes() + + userController := controller.NewUserController(controller.UserControllerConfig{ + CookieDomain: app.context.cookieDomain, + }, apiRouter, app.services.authService) + + userController.SetupRoutes() + + resourcesController := controller.NewResourcesController(controller.ResourcesControllerConfig{ + ResourcesDir: app.config.ResourcesDir, + ResourcesDisabled: app.config.DisableResources, + }, &engine.RouterGroup) + + resourcesController.SetupRoutes() + + healthController := controller.NewHealthController(apiRouter) + + healthController.SetupRoutes() + + return engine, nil +} diff --git a/internal/bootstrap/service_bootstrap.go b/internal/bootstrap/service_bootstrap.go new file mode 100644 index 0000000..99d07de --- /dev/null +++ b/internal/bootstrap/service_bootstrap.go @@ -0,0 +1,100 @@ +package bootstrap + +import ( + "tinyauth/internal/service" + + "github.com/rs/zerolog/log" +) + +type Services struct { + accessControlService *service.AccessControlsService + authService *service.AuthService + databaseService *service.DatabaseService + dockerService *service.DockerService + ldapService *service.LdapService + oauthBrokerService *service.OAuthBrokerService +} + +func (app *BootstrapApp) initServices() (Services, error) { + services := Services{} + + databaseService := service.NewDatabaseService(service.DatabaseServiceConfig{ + DatabasePath: app.config.DatabasePath, + }) + + err := databaseService.Init() + + if err != nil { + return Services{}, err + } + + services.databaseService = databaseService + + ldapService := service.NewLdapService(service.LdapServiceConfig{ + Address: app.config.LdapAddress, + BindDN: app.config.LdapBindDN, + BindPassword: app.config.LdapBindPassword, + BaseDN: app.config.LdapBaseDN, + Insecure: app.config.LdapInsecure, + SearchFilter: app.config.LdapSearchFilter, + }) + + err = ldapService.Init() + + if err == nil { + services.ldapService = ldapService + } else { + log.Warn().Err(err).Msg("Failed to initialize LDAP service, continuing without it") + } + + dockerService := service.NewDockerService() + + err = dockerService.Init() + + if err != nil { + return Services{}, err + } + + services.dockerService = dockerService + + accessControlsService := service.NewAccessControlsService(dockerService) + + err = accessControlsService.Init() + + if err != nil { + return Services{}, err + } + + services.accessControlService = accessControlsService + + authService := service.NewAuthService(service.AuthServiceConfig{ + Users: app.context.users, + OauthWhitelist: app.config.OAuthWhitelist, + SessionExpiry: app.config.SessionExpiry, + SecureCookie: app.config.SecureCookie, + CookieDomain: app.context.cookieDomain, + LoginTimeout: app.config.LoginTimeout, + LoginMaxRetries: app.config.LoginMaxRetries, + SessionCookieName: app.context.sessionCookieName, + }, dockerService, ldapService, databaseService.GetDatabase()) + + err = authService.Init() + + if err != nil { + return Services{}, err + } + + services.authService = authService + + oauthBrokerService := service.NewOAuthBrokerService(app.context.oauthProviders) + + err = oauthBrokerService.Init() + + if err != nil { + return Services{}, err + } + + services.oauthBrokerService = oauthBrokerService + + return services, nil +}