From 59d2bce189854b55f27102e893b6f56ba51a0dc1 Mon Sep 17 00:00:00 2001 From: Stavros Date: Fri, 4 Jul 2025 02:29:02 +0300 Subject: [PATCH] refactor: remove init functions from methods --- cmd/root.go | 28 +++---- internal/auth/auth.go | 32 ++++---- internal/docker/docker.go | 21 ++--- internal/handlers/handlers.go | 16 ++-- internal/hooks/hooks.go | 12 +-- internal/oauth/oauth.go | 33 ++++---- internal/providers/providers.go | 55 ++++++------- internal/{api/api.go => server/server.go} | 78 ++++++++----------- .../api_test.go => server/server_test.go} | 54 ++++++------- internal/types/config.go | 4 +- 10 files changed, 147 insertions(+), 186 deletions(-) rename internal/{api/api.go => server/server.go} (64%) rename internal/{api/api_test.go => server/server_test.go} (90%) diff --git a/cmd/root.go b/cmd/root.go index a15bec7..4b9ca0d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,13 +8,13 @@ import ( "time" totpCmd "tinyauth/cmd/totp" userCmd "tinyauth/cmd/user" - "tinyauth/internal/api" "tinyauth/internal/auth" "tinyauth/internal/constants" "tinyauth/internal/docker" "tinyauth/internal/handlers" "tinyauth/internal/hooks" "tinyauth/internal/providers" + "tinyauth/internal/server" "tinyauth/internal/types" "tinyauth/internal/utils" @@ -114,8 +114,8 @@ var rootCmd = &cobra.Command{ RedirectCookieName: redirectCookieName, } - // Create api config - apiConfig := types.APIConfig{ + // Create server config + serverConfig := types.ServerConfig{ Port: config.Port, Address: config.Address, } @@ -140,10 +140,7 @@ var rootCmd = &cobra.Command{ } // Create docker service - docker := docker.NewDocker() - - // Initialize docker - err = docker.Init() + docker, err := docker.NewDocker() HandleError(err, "Failed to initialize docker") // Create auth service @@ -152,24 +149,19 @@ var rootCmd = &cobra.Command{ // Create OAuth providers service providers := providers.NewProviders(oauthConfig) - // Initialize providers - providers.Init() - // Create hooks service hooks := hooks.NewHooks(hooksConfig, auth, providers) // Create handlers handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker) - // Create API - api := api.NewAPI(apiConfig, handlers) + // Create server + srv, err := server.NewServer(serverConfig, handlers) + HandleError(err, "Failed to create server") - // Setup routes - api.Init() - api.SetupRoutes() - - // Start - api.Run() + // Start server + err = srv.Start() + HandleError(err, "Failed to start server") }, } diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 2441d12..441bbb8 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -16,36 +16,38 @@ import ( "golang.org/x/crypto/bcrypt" ) -func NewAuth(config types.AuthConfig, docker *docker.Docker) *Auth { - return &Auth{ - Config: config, - Docker: docker, - LoginAttempts: make(map[string]*types.LoginAttempt), - } -} - type Auth struct { Config types.AuthConfig Docker *docker.Docker LoginAttempts map[string]*types.LoginAttempt LoginMutex sync.RWMutex + Store *sessions.CookieStore } -func (auth *Auth) GetSession(c *gin.Context) (*sessions.Session, error) { +func NewAuth(config types.AuthConfig, docker *docker.Docker) *Auth { // Create cookie store - store := sessions.NewCookieStore([]byte(auth.Config.HMACSecret), []byte(auth.Config.EncryptionSecret)) + store := sessions.NewCookieStore([]byte(config.HMACSecret), []byte(config.EncryptionSecret)) // Configure cookie store store.Options = &sessions.Options{ Path: "/", - MaxAge: auth.Config.SessionExpiry, - Secure: auth.Config.CookieSecure, + MaxAge: config.SessionExpiry, + Secure: config.CookieSecure, HttpOnly: true, - Domain: fmt.Sprintf(".%s", auth.Config.Domain), + Domain: fmt.Sprintf(".%s", config.Domain), } + return &Auth{ + Config: config, + Docker: docker, + LoginAttempts: make(map[string]*types.LoginAttempt), + Store: store, + } +} + +func (auth *Auth) GetSession(c *gin.Context) (*sessions.Session, error) { // Get session - session, err := store.Get(c.Request, auth.Config.SessionCookieName) + session, err := auth.Store.Get(c.Request, auth.Config.SessionCookieName) if err != nil { log.Warn().Err(err).Msg("Invalid session, clearing cookie and retrying") @@ -54,7 +56,7 @@ func (auth *Auth) GetSession(c *gin.Context) (*sessions.Session, error) { c.SetCookie(auth.Config.SessionCookieName, "", -1, "/", fmt.Sprintf(".%s", auth.Config.Domain), auth.Config.CookieSecure, true) // Try to get the session again - session, err = store.Get(c.Request, auth.Config.SessionCookieName) + session, err = auth.Store.Get(c.Request, auth.Config.SessionCookieName) if err != nil { // If we still can't get the session, log the error and return nil diff --git a/internal/docker/docker.go b/internal/docker/docker.go index 1c2511a..423fe0c 100644 --- a/internal/docker/docker.go +++ b/internal/docker/docker.go @@ -11,35 +11,30 @@ import ( "github.com/rs/zerolog/log" ) -func NewDocker() *Docker { - return &Docker{} -} - type Docker struct { Client *client.Client Context context.Context } -func (docker *Docker) Init() error { +func NewDocker() (*Docker, error) { // Create a new docker client client, err := client.NewClientWithOpts(client.FromEnv) // Check if there was an error if err != nil { - return err + return nil, err } // Create the context - docker.Context = context.Background() + ctx := context.Background() // Negotiate API version - client.NegotiateAPIVersion(docker.Context) + client.NegotiateAPIVersion(ctx) - // Set client - docker.Client = client - - // Done - return nil + return &Docker{ + Client: client, + Context: ctx, + }, nil } func (docker *Docker) GetContainers() ([]container.Summary, error) { diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index aac5f17..a2a4ca7 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -18,6 +18,14 @@ import ( "github.com/rs/zerolog/log" ) +type Handlers struct { + Config types.HandlersConfig + Auth *auth.Auth + Hooks *hooks.Hooks + Providers *providers.Providers + Docker *docker.Docker +} + func NewHandlers(config types.HandlersConfig, auth *auth.Auth, hooks *hooks.Hooks, providers *providers.Providers, docker *docker.Docker) *Handlers { return &Handlers{ Config: config, @@ -28,14 +36,6 @@ func NewHandlers(config types.HandlersConfig, auth *auth.Auth, hooks *hooks.Hook } } -type Handlers struct { - Config types.HandlersConfig - Auth *auth.Auth - Hooks *hooks.Hooks - Providers *providers.Providers - Docker *docker.Docker -} - func (h *Handlers) AuthHandler(c *gin.Context) { // Create struct for proxy var proxy types.Proxy diff --git a/internal/hooks/hooks.go b/internal/hooks/hooks.go index 15947be..04d78e6 100644 --- a/internal/hooks/hooks.go +++ b/internal/hooks/hooks.go @@ -12,6 +12,12 @@ import ( "github.com/rs/zerolog/log" ) +type Hooks struct { + Config types.HooksConfig + Auth *auth.Auth + Providers *providers.Providers +} + func NewHooks(config types.HooksConfig, auth *auth.Auth, providers *providers.Providers) *Hooks { return &Hooks{ Config: config, @@ -20,12 +26,6 @@ func NewHooks(config types.HooksConfig, auth *auth.Auth, providers *providers.Pr } } -type Hooks struct { - Config types.HooksConfig - Auth *auth.Auth - Providers *providers.Providers -} - func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext { // Get session cookie and basic auth cookie, err := hooks.Auth.GetSessionCookie(c) diff --git a/internal/oauth/oauth.go b/internal/oauth/oauth.go index 3004f71..28faebb 100644 --- a/internal/oauth/oauth.go +++ b/internal/oauth/oauth.go @@ -10,32 +10,24 @@ import ( "golang.org/x/oauth2" ) -func NewOAuth(config oauth2.Config, insecureSkipVerify bool) *OAuth { - return &OAuth{ - Config: config, - InsecureSkipVerify: insecureSkipVerify, - } -} - type OAuth struct { - Config oauth2.Config - Context context.Context - Token *oauth2.Token - Verifier string - InsecureSkipVerify bool + Config oauth2.Config + Context context.Context + Token *oauth2.Token + Verifier string } -func (oauth *OAuth) Init() { +func NewOAuth(config oauth2.Config, insecureSkipVerify bool) *OAuth { // Create transport with TLS transport := &http.Transport{ TLSClientConfig: &tls.Config{ - InsecureSkipVerify: oauth.InsecureSkipVerify, + InsecureSkipVerify: insecureSkipVerify, MinVersion: tls.VersionTLS12, }, } // Create a new context - oauth.Context = context.Background() + ctx := context.Background() // Create the HTTP client with the transport httpClient := &http.Client{ @@ -43,9 +35,16 @@ func (oauth *OAuth) Init() { } // Set the HTTP client in the context - oauth.Context = context.WithValue(oauth.Context, oauth2.HTTPClient, httpClient) + ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient) + // Create the verifier - oauth.Verifier = oauth2.GenerateVerifier() + verifier := oauth2.GenerateVerifier() + + return &OAuth{ + Config: config, + Context: ctx, + Verifier: verifier, + } } func (oauth *OAuth) GetAuthURL(state string) string { diff --git a/internal/providers/providers.go b/internal/providers/providers.go index 8369837..3cbefb6 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -11,12 +11,6 @@ import ( "golang.org/x/oauth2/endpoints" ) -func NewProviders(config types.OAuthConfig) *Providers { - return &Providers{ - Config: config, - } -} - type Providers struct { Config types.OAuthConfig Github *oauth.OAuth @@ -24,60 +18,57 @@ type Providers struct { Generic *oauth.OAuth } -func (providers *Providers) Init() { +func NewProviders(config types.OAuthConfig) *Providers { + providers := &Providers{ + Config: config, + } + // If we have a client id and secret for github, initialize the oauth provider - if providers.Config.GithubClientId != "" && providers.Config.GithubClientSecret != "" { + if config.GithubClientId != "" && config.GithubClientSecret != "" { log.Info().Msg("Initializing Github OAuth") // Create a new oauth provider with the github config providers.Github = oauth.NewOAuth(oauth2.Config{ - ClientID: providers.Config.GithubClientId, - ClientSecret: providers.Config.GithubClientSecret, - RedirectURL: fmt.Sprintf("%s/api/oauth/callback/github", providers.Config.AppURL), + ClientID: config.GithubClientId, + ClientSecret: config.GithubClientSecret, + RedirectURL: fmt.Sprintf("%s/api/oauth/callback/github", config.AppURL), Scopes: GithubScopes(), Endpoint: endpoints.GitHub, }, false) - - // Initialize the oauth provider - providers.Github.Init() } // If we have a client id and secret for google, initialize the oauth provider - if providers.Config.GoogleClientId != "" && providers.Config.GoogleClientSecret != "" { + if config.GoogleClientId != "" && config.GoogleClientSecret != "" { log.Info().Msg("Initializing Google OAuth") // Create a new oauth provider with the google config providers.Google = oauth.NewOAuth(oauth2.Config{ - ClientID: providers.Config.GoogleClientId, - ClientSecret: providers.Config.GoogleClientSecret, - RedirectURL: fmt.Sprintf("%s/api/oauth/callback/google", providers.Config.AppURL), + ClientID: config.GoogleClientId, + ClientSecret: config.GoogleClientSecret, + RedirectURL: fmt.Sprintf("%s/api/oauth/callback/google", config.AppURL), Scopes: GoogleScopes(), Endpoint: endpoints.Google, }, false) - - // Initialize the oauth provider - providers.Google.Init() } // If we have a client id and secret for generic oauth, initialize the oauth provider - if providers.Config.GenericClientId != "" && providers.Config.GenericClientSecret != "" { + if config.GenericClientId != "" && config.GenericClientSecret != "" { log.Info().Msg("Initializing Generic OAuth") // Create a new oauth provider with the generic config providers.Generic = oauth.NewOAuth(oauth2.Config{ - ClientID: providers.Config.GenericClientId, - ClientSecret: providers.Config.GenericClientSecret, - RedirectURL: fmt.Sprintf("%s/api/oauth/callback/generic", providers.Config.AppURL), - Scopes: providers.Config.GenericScopes, + ClientID: config.GenericClientId, + ClientSecret: config.GenericClientSecret, + RedirectURL: fmt.Sprintf("%s/api/oauth/callback/generic", config.AppURL), + Scopes: config.GenericScopes, Endpoint: oauth2.Endpoint{ - AuthURL: providers.Config.GenericAuthURL, - TokenURL: providers.Config.GenericTokenURL, + AuthURL: config.GenericAuthURL, + TokenURL: config.GenericTokenURL, }, - }, providers.Config.GenericSkipSSL) - - // Initialize the oauth provider - providers.Generic.Init() + }, config.GenericSkipSSL) } + + return providers } func (providers *Providers) GetProvider(provider string) *oauth.OAuth { diff --git a/internal/api/api.go b/internal/server/server.go similarity index 64% rename from internal/api/api.go rename to internal/server/server.go index 1b6aacf..d188447 100644 --- a/internal/api/api.go +++ b/internal/server/server.go @@ -1,4 +1,4 @@ -package api +package server import ( "fmt" @@ -15,20 +15,13 @@ import ( "github.com/rs/zerolog/log" ) -func NewAPI(config types.APIConfig, handlers *handlers.Handlers) *API { - return &API{ - Config: config, - Handlers: handlers, - } -} - -type API struct { - Config types.APIConfig - Router *gin.Engine +type Server struct { + Config types.ServerConfig Handlers *handlers.Handlers + Router *gin.Engine } -func (api *API) Init() { +func NewServer(config types.ServerConfig, handlers *handlers.Handlers) (*Server, error) { // Disable gin logs gin.SetMode(gin.ReleaseMode) @@ -42,7 +35,7 @@ func (api *API) Init() { dist, err := fs.Sub(assets.Assets, "dist") if err != nil { - log.Fatal().Err(err).Msg("Failed to get UI assets") + return nil, err } // Create file server @@ -69,41 +62,38 @@ func (api *API) Init() { } }) - // Set router - api.Router = router + // Proxy routes + router.GET("/api/auth/:proxy", handlers.AuthHandler) + + // Auth routes + router.POST("/api/login", handlers.LoginHandler) + router.POST("/api/totp", handlers.TotpHandler) + router.POST("/api/logout", handlers.LogoutHandler) + + // Context routes + router.GET("/api/app", handlers.AppHandler) + router.GET("/api/user", handlers.UserHandler) + + // OAuth routes + router.GET("/api/oauth/url/:provider", handlers.OauthUrlHandler) + router.GET("/api/oauth/callback/:provider", handlers.OauthCallbackHandler) + + // App routes + router.GET("/api/healthcheck", handlers.HealthcheckHandler) + + // Return the server + return &Server{ + Config: config, + Handlers: handlers, + Router: router, + }, nil } -func (api *API) SetupRoutes() { - // Proxy - api.Router.GET("/api/auth/:proxy", api.Handlers.AuthHandler) - - // Auth - api.Router.POST("/api/login", api.Handlers.LoginHandler) - api.Router.POST("/api/totp", api.Handlers.TotpHandler) - api.Router.POST("/api/logout", api.Handlers.LogoutHandler) - - // Context - api.Router.GET("/api/app", api.Handlers.AppHandler) - api.Router.GET("/api/user", api.Handlers.UserHandler) - - // OAuth - api.Router.GET("/api/oauth/url/:provider", api.Handlers.OauthUrlHandler) - api.Router.GET("/api/oauth/callback/:provider", api.Handlers.OauthCallbackHandler) - - // App - api.Router.GET("/api/healthcheck", api.Handlers.HealthcheckHandler) -} - -func (api *API) Run() { - log.Info().Str("address", api.Config.Address).Int("port", api.Config.Port).Msg("Starting server") - +func (s *Server) Start() error { // Run server - err := api.Router.Run(fmt.Sprintf("%s:%d", api.Config.Address, api.Config.Port)) + log.Info().Str("address", s.Config.Address).Int("port", s.Config.Port).Msg("Starting server") - // Check for errors - if err != nil { - log.Fatal().Err(err).Msg("Failed to start server") - } + return s.Router.Run(fmt.Sprintf("%s:%d", s.Config.Address, s.Config.Port)) } // zerolog is a middleware for gin that logs requests using zerolog diff --git a/internal/api/api_test.go b/internal/server/server_test.go similarity index 90% rename from internal/api/api_test.go rename to internal/server/server_test.go index 42b9e6a..dad0167 100644 --- a/internal/api/api_test.go +++ b/internal/server/server_test.go @@ -1,4 +1,4 @@ -package api_test +package server_test import ( "encoding/json" @@ -8,19 +8,19 @@ import ( "reflect" "strings" "testing" - "tinyauth/internal/api" "tinyauth/internal/auth" "tinyauth/internal/docker" "tinyauth/internal/handlers" "tinyauth/internal/hooks" "tinyauth/internal/providers" + "tinyauth/internal/server" "tinyauth/internal/types" "github.com/magiconair/properties/assert" ) -// Simple API config for tests -var apiConfig = types.APIConfig{ +// Simple server config for tests +var serverConfig = types.ServerConfig{ Port: 8080, Address: "0.0.0.0", } @@ -68,15 +68,11 @@ var user = types.User{ Password: "$2a$10$AvGHLTYv3xiRJ0xV9xs3XeVIlkGTygI9nqIamFYB5Xu.5.0UWF7B6", // pass } -// We need all this to be able to test the API -func getAPI(t *testing.T) *api.API { +// We need all this to be able to test the server +func getServer(t *testing.T) *server.Server { // Create docker service - docker := docker.NewDocker() + docker, err := docker.NewDocker() - // Initialize docker - err := docker.Init() - - // Check if there was an error if err != nil { t.Fatalf("Failed to initialize docker: %v", err) } @@ -93,31 +89,29 @@ func getAPI(t *testing.T) *api.API { // Create providers service providers := providers.NewProviders(types.OAuthConfig{}) - // Initialize providers - providers.Init() - // Create hooks service hooks := hooks.NewHooks(hooksConfig, auth, providers) // Create handlers service handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker) - // Create API - api := api.NewAPI(apiConfig, handlers) + // Create server + srv, err := server.NewServer(serverConfig, handlers) - // Setup routes - api.Init() - api.SetupRoutes() + if err != nil { + t.Fatalf("Failed to create server: %v", err) + } - return api + // Return the server + return srv } -// Test login (we will need this for the other tests) +// Test login func TestLogin(t *testing.T) { t.Log("Testing login") - // Get API - api := getAPI(t) + // Get server + api := getServer(t) // Create recorder recorder := httptest.NewRecorder() @@ -162,8 +156,8 @@ func TestLogin(t *testing.T) { func TestAppContext(t *testing.T) { t.Log("Testing app context") - // Get API - api := getAPI(t) + // Get server + api := getServer(t) // Create recorder recorder := httptest.NewRecorder() @@ -230,8 +224,8 @@ func TestAppContext(t *testing.T) { func TestUserContext(t *testing.T) { t.Log("Testing user context") - // Get API - api := getAPI(t) + // Get server + api := getServer(t) // Create recorder recorder := httptest.NewRecorder() @@ -288,8 +282,8 @@ func TestUserContext(t *testing.T) { func TestLogout(t *testing.T) { t.Log("Testing logout") - // Get API - api := getAPI(t) + // Get server + api := getServer(t) // Create recorder recorder := httptest.NewRecorder() @@ -319,5 +313,3 @@ func TestLogout(t *testing.T) { t.Fatalf("Cookie not flushed") } } - -// TODO: Testing for the oauth stuff diff --git a/internal/types/config.go b/internal/types/config.go index 12c560d..91b8cf7 100644 --- a/internal/types/config.go +++ b/internal/types/config.go @@ -69,8 +69,8 @@ type OAuthConfig struct { AppURL string } -// APIConfig is the configuration for the API -type APIConfig struct { +// ServerConfig is the configuration for the server +type ServerConfig struct { Port int Address string }