mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-28 04:35:40 +00:00
refactor: rework file structure (#325)
* wip: add middlewares * refactor: use context fom middleware in handlers * refactor: use controller approach in handlers * refactor: move oauth providers into services (non-working) * feat: create oauth broker service * refactor: use a boostrap service to bootstrap the app * refactor: split utils into smaller files * refactor: use more clear name for frontend assets * feat: allow customizability of resources dir * fix: fix typo in ui middleware * fix: validate resource file paths in ui middleware * refactor: move resource handling to a controller * feat: add some logging * fix: configure middlewares before groups * fix: use correct api path in login mutation * fix: coderabbit suggestions * fix: further coderabbit suggestions
This commit is contained in:
159
internal/middleware/context_middleware.go
Normal file
159
internal/middleware/context_middleware.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"tinyauth/internal/config"
|
||||
"tinyauth/internal/service"
|
||||
"tinyauth/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type ContextMiddlewareConfig struct {
|
||||
Domain string
|
||||
}
|
||||
|
||||
type ContextMiddleware struct {
|
||||
Config ContextMiddlewareConfig
|
||||
Auth *service.AuthService
|
||||
Broker *service.OAuthBrokerService
|
||||
}
|
||||
|
||||
func NewContextMiddleware(config ContextMiddlewareConfig, auth *service.AuthService, broker *service.OAuthBrokerService) *ContextMiddleware {
|
||||
return &ContextMiddleware{
|
||||
Config: config,
|
||||
Auth: auth,
|
||||
Broker: broker,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ContextMiddleware) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
cookie, err := m.Auth.GetSessionCookie(c)
|
||||
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("No valid session cookie found")
|
||||
goto basic
|
||||
}
|
||||
|
||||
if cookie.TotpPending {
|
||||
c.Set("context", &config.UserContext{
|
||||
Username: cookie.Username,
|
||||
Name: cookie.Name,
|
||||
Email: cookie.Email,
|
||||
Provider: "username",
|
||||
TotpPending: true,
|
||||
TotpEnabled: true,
|
||||
})
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
switch cookie.Provider {
|
||||
case "username":
|
||||
userSearch := m.Auth.SearchUser(cookie.Username)
|
||||
|
||||
if userSearch.Type == "unknown" {
|
||||
log.Debug().Msg("User from session cookie not found")
|
||||
m.Auth.DeleteSessionCookie(c)
|
||||
goto basic
|
||||
}
|
||||
|
||||
c.Set("context", &config.UserContext{
|
||||
Username: cookie.Username,
|
||||
Name: cookie.Name,
|
||||
Email: cookie.Email,
|
||||
Provider: "username",
|
||||
IsLoggedIn: true,
|
||||
})
|
||||
c.Next()
|
||||
return
|
||||
default:
|
||||
_, exists := m.Broker.GetService(cookie.Provider)
|
||||
|
||||
if !exists {
|
||||
log.Debug().Msg("OAuth provider from session cookie not found")
|
||||
m.Auth.DeleteSessionCookie(c)
|
||||
goto basic
|
||||
}
|
||||
|
||||
if !m.Auth.EmailWhitelisted(cookie.Email) {
|
||||
log.Debug().Msg("Email from session cookie not whitelisted")
|
||||
m.Auth.DeleteSessionCookie(c)
|
||||
goto basic
|
||||
}
|
||||
|
||||
c.Set("context", &config.UserContext{
|
||||
Username: cookie.Username,
|
||||
Name: cookie.Name,
|
||||
Email: cookie.Email,
|
||||
Provider: cookie.Provider,
|
||||
OAuthGroups: cookie.OAuthGroups,
|
||||
IsLoggedIn: true,
|
||||
OAuth: true,
|
||||
})
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
basic:
|
||||
basic := m.Auth.GetBasicAuth(c)
|
||||
|
||||
if basic == nil {
|
||||
log.Debug().Msg("No basic auth provided")
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
userSearch := m.Auth.SearchUser(basic.Username)
|
||||
|
||||
if userSearch.Type == "unknown" {
|
||||
log.Debug().Msg("User from basic auth not found")
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
if !m.Auth.VerifyUser(userSearch, basic.Password) {
|
||||
log.Debug().Msg("Invalid password for basic auth user")
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
switch userSearch.Type {
|
||||
case "local":
|
||||
log.Debug().Msg("Basic auth user is local")
|
||||
|
||||
user := m.Auth.GetLocalUser(basic.Username)
|
||||
|
||||
c.Set("context", &config.UserContext{
|
||||
Username: user.Username,
|
||||
Name: utils.Capitalize(user.Username),
|
||||
Email: fmt.Sprintf("%s@%s", strings.ToLower(user.Username), m.Config.Domain),
|
||||
Provider: "basic",
|
||||
IsLoggedIn: true,
|
||||
TotpEnabled: user.TotpSecret != "",
|
||||
})
|
||||
c.Next()
|
||||
return
|
||||
case "ldap":
|
||||
log.Debug().Msg("Basic auth user is LDAP")
|
||||
c.Set("context", &config.UserContext{
|
||||
Username: basic.Username,
|
||||
Name: utils.Capitalize(basic.Username),
|
||||
Email: fmt.Sprintf("%s@%s", strings.ToLower(basic.Username), m.Config.Domain),
|
||||
Provider: "basic",
|
||||
IsLoggedIn: true,
|
||||
})
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
56
internal/middleware/ui_middleware.go
Normal file
56
internal/middleware/ui_middleware.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"tinyauth/internal/assets"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type UIMiddleware struct {
|
||||
UIFS fs.FS
|
||||
UIFileServer http.Handler
|
||||
}
|
||||
|
||||
func NewUIMiddleware() *UIMiddleware {
|
||||
return &UIMiddleware{}
|
||||
}
|
||||
|
||||
func (m *UIMiddleware) Init() error {
|
||||
ui, err := fs.Sub(assets.FrontendAssets, "dist")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.UIFS = ui
|
||||
m.UIFileServer = http.FileServer(http.FS(ui))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *UIMiddleware) Middleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
switch strings.Split(c.Request.URL.Path, "/")[1] {
|
||||
case "api":
|
||||
c.Next()
|
||||
return
|
||||
case "resources":
|
||||
c.Next()
|
||||
return
|
||||
default:
|
||||
_, err := fs.Stat(m.UIFS, strings.TrimPrefix(c.Request.URL.Path, "/"))
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
c.Request.URL.Path = "/"
|
||||
}
|
||||
|
||||
m.UIFileServer.ServeHTTP(c.Writer, c.Request)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
66
internal/middleware/zerolog_middleware.go
Normal file
66
internal/middleware/zerolog_middleware.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var (
|
||||
loggerSkipPathsPrefix = []string{
|
||||
"GET /api/health",
|
||||
"HEAD /api/health",
|
||||
"GET /favicon.ico",
|
||||
}
|
||||
)
|
||||
|
||||
type ZerologMiddleware struct{}
|
||||
|
||||
func NewZerologMiddleware() *ZerologMiddleware {
|
||||
return &ZerologMiddleware{}
|
||||
}
|
||||
|
||||
func (m *ZerologMiddleware) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ZerologMiddleware) logPath(path string) bool {
|
||||
for _, prefix := range loggerSkipPathsPrefix {
|
||||
if strings.HasPrefix(path, prefix) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *ZerologMiddleware) Middleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
tStart := time.Now()
|
||||
|
||||
c.Next()
|
||||
|
||||
code := c.Writer.Status()
|
||||
address := c.Request.RemoteAddr
|
||||
clientIP := c.ClientIP()
|
||||
method := c.Request.Method
|
||||
path := c.Request.URL.Path
|
||||
|
||||
latency := time.Since(tStart).String()
|
||||
|
||||
// logPath check if the path should be logged normally or with debug
|
||||
if m.logPath(method + " " + path) {
|
||||
switch {
|
||||
case code >= 200 && code < 300:
|
||||
log.Info().Str("method", method).Str("path", path).Str("address", address).Str("clientIp", clientIP).Int("status", code).Str("latency", latency).Msg("Request")
|
||||
case code >= 300 && code < 400:
|
||||
log.Warn().Str("method", method).Str("path", path).Str("address", address).Str("clientIp", clientIP).Int("status", code).Str("latency", latency).Msg("Request")
|
||||
case code >= 400:
|
||||
log.Error().Str("method", method).Str("path", path).Str("address", address).Str("clientIp", clientIP).Int("status", code).Str("latency", latency).Msg("Request")
|
||||
}
|
||||
} else {
|
||||
log.Debug().Str("method", method).Str("path", path).Str("address", address).Int("status", code).Str("latency", latency).Msg("Request")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user