diff --git a/air.toml b/air.toml index 7505b79..f84163b 100644 --- a/air.toml +++ b/air.toml @@ -4,7 +4,7 @@ tmp_dir = "tmp" [build] pre_cmd = ["mkdir -p internal/assets/dist", "echo 'backend running' > internal/assets/dist/index.html", "go install github.com/go-delve/delve/cmd/dlv@v1.25.0"] cmd = "CGO_ENABLED=0 go build -gcflags=\"all=-N -l\" -o tmp/tinyauth ." -bin = "/go/bin/dlv --listen :4000 --headless=true --api-version=2 --accept-multiclient --log=true exec tmp/tinyauth --continue" +bin = "/go/bin/dlv --listen :4000 --headless=true --api-version=2 --accept-multiclient --log=true exec tmp/tinyauth --continue --check-go-version=false" include_ext = ["go"] exclude_dir = ["internal/assets/dist"] exclude_regex = [".*_test\\.go"] diff --git a/cmd/root.go b/cmd/root.go index f96ec6b..927b375 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -10,8 +10,8 @@ import ( "tinyauth/internal/constants" "tinyauth/internal/docker" "tinyauth/internal/handlers" - "tinyauth/internal/hooks" "tinyauth/internal/ldap" + "tinyauth/internal/middleware" "tinyauth/internal/providers" "tinyauth/internal/server" "tinyauth/internal/types" @@ -84,7 +84,7 @@ var rootCmd = &cobra.Command{ AppURL: config.AppURL, } - handlersConfig := types.HandlersConfig{ + handlersConfig := handlers.HandlersConfig{ AppURL: config.AppURL, DisableContinue: config.DisableContinue, Title: config.Title, @@ -116,10 +116,6 @@ var rootCmd = &cobra.Command{ EncryptionSecret: encryptionSecret, } - hooksConfig := types.HooksConfig{ - Domain: domain, - } - var ldapService *ldap.LDAP if config.LdapAddress != "" { @@ -151,9 +147,20 @@ var rootCmd = &cobra.Command{ HandleError(err, "Failed to initialize docker") auth := auth.NewAuth(authConfig, docker, ldapService) providers := providers.NewProviders(oauthConfig) - hooks := hooks.NewHooks(hooksConfig, auth, providers) - handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker) - srv, err := server.NewServer(serverConfig, handlers) + handlers := handlers.NewHandlers(handlersConfig, auth, providers, docker) + + // Setup the middlewares + var middlewares []server.Middleware + + contextMiddleware := middleware.NewContextMiddleware(middleware.ContextMiddlewareConfig{ + Domain: domain, + }, auth, providers) + uiMiddleware := middleware.NewUIMiddleware() + zerologMiddleware := middleware.NewZerologMiddleware() + + middlewares = append(middlewares, contextMiddleware, uiMiddleware, zerologMiddleware) + + srv, err := server.NewServer(serverConfig, handlers, middlewares) HandleError(err, "Failed to create server") // Start up diff --git a/internal/handlers/context.go b/internal/handlers/context.go index d0fff5e..0bbe392 100644 --- a/internal/handlers/context.go +++ b/internal/handlers/context.go @@ -37,8 +37,28 @@ func (h *Handlers) AppContextHandler(c *gin.Context) { func (h *Handlers) UserContextHandler(c *gin.Context) { log.Debug().Msg("Getting user context") - // Create user context using hooks - userContext := h.Hooks.UseUserContext(c) + // Get user context from middleware + userContextValue, exists := c.Get("context") + + if !exists { + c.JSON(200, types.UserContextResponse{ + Status: 200, + Message: "Unauthorized", + IsLoggedIn: false, + }) + return + } + + userContext, ok := userContextValue.(*types.UserContext) + + if !ok { + c.JSON(200, types.UserContextResponse{ + Status: 200, + Message: "Unauthorized", + IsLoggedIn: false, + }) + return + } userContextResponse := types.UserContextResponse{ Status: 200, diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 0e8ebe2..e24f7fa 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -3,26 +3,36 @@ package handlers import ( "tinyauth/internal/auth" "tinyauth/internal/docker" - "tinyauth/internal/hooks" "tinyauth/internal/providers" - "tinyauth/internal/types" "github.com/gin-gonic/gin" ) +type HandlersConfig struct { + AppURL string + Domain string + CookieSecure bool + DisableContinue bool + GenericName string + Title string + ForgotPasswordMessage string + BackgroundImage string + OAuthAutoRedirect string + CsrfCookieName string + RedirectCookieName string +} + type Handlers struct { - Config types.HandlersConfig + Config 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 { +func NewHandlers(config HandlersConfig, auth *auth.Auth, providers *providers.Providers, docker *docker.Docker) *Handlers { return &Handlers{ Config: config, Auth: auth, - Hooks: hooks, Providers: providers, Docker: docker, } diff --git a/internal/handlers/proxy.go b/internal/handlers/proxy.go index fd87fd1..c9d234e 100644 --- a/internal/handlers/proxy.go +++ b/internal/handlers/proxy.go @@ -146,7 +146,24 @@ func (h *Handlers) ProxyHandler(c *gin.Context) { return } - userContext := h.Hooks.UseUserContext(c) + var userContext *types.UserContext + + userContextValue, exists := c.Get("context") + + if !exists { + userContext = &types.UserContext{ + IsLoggedIn: false, + } + } else { + var ok bool + userContext, ok = userContextValue.(*types.UserContext) + + if !ok { + userContext = &types.UserContext{ + IsLoggedIn: false, + } + } + } // If we are using basic auth, we need to check if the user has totp and if it does then disable basic auth if userContext.Provider == "basic" && userContext.TotpEnabled { @@ -158,7 +175,7 @@ func (h *Handlers) ProxyHandler(c *gin.Context) { log.Debug().Msg("Authenticated") // Check if user is allowed to access subdomain, if request is nginx.example.com the subdomain (resource) is nginx - appAllowed := h.Auth.ResourceAllowed(c, userContext, labels) + appAllowed := h.Auth.ResourceAllowed(c, *userContext, labels) log.Debug().Bool("appAllowed", appAllowed).Msg("Checking if app is allowed") @@ -195,7 +212,7 @@ func (h *Handlers) ProxyHandler(c *gin.Context) { } if userContext.OAuth { - groupOk := h.Auth.OAuthGroup(c, userContext, labels) + groupOk := h.Auth.OAuthGroup(c, *userContext, labels) log.Debug().Bool("groupOk", groupOk).Msg("Checking if user is in required groups") diff --git a/internal/handlers/user.go b/internal/handlers/user.go index 91d0fef..86a18ee 100644 --- a/internal/handlers/user.go +++ b/internal/handlers/user.go @@ -141,7 +141,25 @@ func (h *Handlers) TOTPHandler(c *gin.Context) { log.Debug().Msg("Checking totp") // Get user context - userContext := h.Hooks.UseUserContext(c) + userContextValue, exists := c.Get("context") + + if !exists { + c.JSON(401, gin.H{ + "status": 401, + "message": "Unauthorized", + }) + return + } + + userContext, ok := userContextValue.(*types.UserContext) + + if !ok { + c.JSON(401, gin.H{ + "status": 401, + "message": "Unauthorized", + }) + return + } // Check if we have a user if userContext.Username == "" { @@ -157,7 +175,7 @@ func (h *Handlers) TOTPHandler(c *gin.Context) { user := h.Auth.GetLocalUser(userContext.Username) // Check if totp is correct - ok := totp.Validate(totpReq.Code, user.TotpSecret) + ok = totp.Validate(totpReq.Code, user.TotpSecret) if !ok { log.Debug().Msg("Totp incorrect") diff --git a/internal/middleware/context_middleware.go b/internal/middleware/context_middleware.go index c4b7682..ead4879 100644 --- a/internal/middleware/context_middleware.go +++ b/internal/middleware/context_middleware.go @@ -1,4 +1,4 @@ -package middlewares +package middleware import ( "fmt" @@ -29,6 +29,14 @@ func NewContextMiddleware(config ContextMiddlewareConfig, auth *auth.Auth, provi } } +func (m *ContextMiddleware) Init() error { + return nil +} + +func (m *ContextMiddleware) Name() string { + return "ContextMiddleware" +} + func (m *ContextMiddleware) Middleware() gin.HandlerFunc { return func(c *gin.Context) { cookie, err := m.Auth.GetSessionCookie(c) diff --git a/internal/middleware/ui_middlware.go b/internal/middleware/ui_middlware.go index 2a68782..22f8ca2 100644 --- a/internal/middleware/ui_middlware.go +++ b/internal/middleware/ui_middlware.go @@ -1,4 +1,4 @@ -package middlewares +package middleware import ( "io/fs" @@ -16,24 +16,29 @@ type UIMiddleware struct { ResourcesFileServer http.Handler } -func NewUIMiddleware() (*UIMiddleware, error) { +func NewUIMiddleware() *UIMiddleware { + return &UIMiddleware{} +} + +func (m *UIMiddleware) Init() error { ui, err := fs.Sub(assets.Assets, "dist") if err != nil { - return nil, err + return nil } - uiFileServer := http.FileServer(http.FS(ui)) - resourcesFileServer := http.FileServer(http.Dir("/data/resources")) + m.UIFS = ui + m.UIFileServer = http.FileServer(http.FS(ui)) + m.ResourcesFileServer = http.FileServer(http.Dir("/data/resources")) - return &UIMiddleware{ - UIFS: ui, - UIFileServer: uiFileServer, - ResourcesFileServer: resourcesFileServer, - }, nil + return nil } -func (m UIMiddleware) Middlware() gin.HandlerFunc { +func (m *UIMiddleware) Name() string { + return "UIMiddleware" +} + +func (m *UIMiddleware) Middleware() gin.HandlerFunc { return func(c *gin.Context) { switch strings.Split(c.Request.URL.Path, "/")[1] { case "api": diff --git a/internal/middleware/zerolog_middleware.go b/internal/middleware/zerolog_middleware.go index bca9a12..79c5d70 100644 --- a/internal/middleware/zerolog_middleware.go +++ b/internal/middleware/zerolog_middleware.go @@ -1,4 +1,4 @@ -package middlewares +package middleware import ( "strings" @@ -22,7 +22,15 @@ func NewZerologMiddleware() *ZerologMiddleware { return &ZerologMiddleware{} } -func (m ZerologMiddleware) logPath(path string) bool { +func (m *ZerologMiddleware) Init() error { + return nil +} + +func (m *ZerologMiddleware) Name() string { + return "ZerologMiddleware" +} + +func (m *ZerologMiddleware) logPath(path string) bool { for _, prefix := range loggerSkipPathsPrefix { if strings.HasPrefix(path, prefix) { return false @@ -31,7 +39,7 @@ func (m ZerologMiddleware) logPath(path string) bool { return true } -func (m ZerologMiddleware) Middlware() gin.HandlerFunc { +func (m *ZerologMiddleware) Middleware() gin.HandlerFunc { return func(c *gin.Context) { tStart := time.Now() diff --git a/internal/server/server.go b/internal/server/server.go index a382074..78150fe 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -15,15 +15,22 @@ type Server struct { Router *gin.Engine } -type Middlware interface { - Middlware() gin.HandlerFunc +type Middleware interface { + Middleware() gin.HandlerFunc + Init() error + Name() string } -func NewServer(config types.ServerConfig, handlers *handlers.Handlers, middlewares []Middlware) (*Server, error) { +func NewServer(config types.ServerConfig, handlers *handlers.Handlers, middlewares []Middleware) (*Server, error) { router := gin.New() for _, middleware := range middlewares { - router.Use(middleware.Middlware()) + log.Debug().Str("middleware", middleware.Name()).Msg("Initializing middleware") + err := middleware.Init() + if err != nil { + return nil, fmt.Errorf("failed to initialize middleware %s: %w", middleware.Name(), err) + } + router.Use(middleware.Middleware()) } // Proxy routes diff --git a/internal/types/config.go b/internal/types/config.go index 54ab6c8..4b32ad9 100644 --- a/internal/types/config.go +++ b/internal/types/config.go @@ -44,21 +44,6 @@ type Config struct { LdapSearchFilter string `mapstructure:"ldap-search-filter"` } -// Server configuration -type HandlersConfig struct { - AppURL string - Domain string - CookieSecure bool - DisableContinue bool - GenericName string - Title string - ForgotPasswordMessage string - BackgroundImage string - OAuthAutoRedirect string - CsrfCookieName string - RedirectCookieName string -} - // OAuthConfig is the configuration for the providers type OAuthConfig struct { GithubClientId string