Compare commits

..

1 Commits

Author SHA1 Message Date
Stavros a7f5374acc refactor: use one struct for service deps 2026-06-13 17:14:47 +03:00
40 changed files with 393 additions and 848 deletions
-1
View File
@@ -21,7 +21,6 @@ require (
github.com/stretchr/testify v1.11.1
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298
github.com/weppos/publicsuffix-go v0.50.3
go.uber.org/dig v1.19.0
golang.org/x/crypto v0.52.0
golang.org/x/oauth2 v0.36.0
golang.org/x/tools v0.45.0
-2
View File
@@ -485,8 +485,6 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
+15 -52
View File
@@ -18,7 +18,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/steveiliop56/ding"
"go.uber.org/dig"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/repository"
@@ -32,23 +31,10 @@ import (
// 2. HTTP server listeners - ding.RingNormal
// 3. Networking layers, user and label providers (e.g. ailscale service, kubernetes service) - ding.RingMajor
// 4. Database connection - ding.RingCritical
type Services struct {
accessControlService *service.AccessControlsService
authService *service.AuthService
dockerService *service.DockerService
kubernetesService *service.KubernetesService
ldapService *service.LdapService
oauthBrokerService *service.OAuthBrokerService
oidcService *service.OIDCService
tailscaleService *service.TailscaleService
policyEngine *service.PolicyEngine
}
type BootstrapApp struct {
config model.Config
runtime model.RuntimeConfig
services Services
services service.Services
log *logger.Logger
ctx context.Context
cancel context.CancelFunc
@@ -57,7 +43,9 @@ type BootstrapApp struct {
db *sql.DB
ding *ding.Ding
listeners []Listener
dig *dig.Container
deps struct {
service *service.ServiceDependencies
}
}
func NewBootstrapApp(config model.Config) *BootstrapApp {
@@ -72,11 +60,7 @@ func (app *BootstrapApp) Setup() error {
app.ctx = ctx
app.cancel = cancel
// create the dig container
c := dig.New()
app.dig = c
// create a ding instance
// Create a ding instance
dg := ding.New(ctx)
app.ding = dg
@@ -163,6 +147,12 @@ func (app *BootstrapApp) Setup() error {
app.runtime.OAuthProviders[id] = provider
}
// setup oidc clients
for id, client := range app.config.OIDC.Clients {
client.ID = id
app.runtime.OIDCClients = append(app.runtime.OIDCClients, client)
}
// cookie domain
cookieDomainResolver := utils.GetCookieDomain
@@ -211,33 +201,6 @@ func (app *BootstrapApp) Setup() error {
// store
app.queries = store
// provide basic utilities to container
type utilityProvider struct {
dig.Out
Log *logger.Logger
Config *model.Config
Runtime *model.RuntimeConfig
Ding *ding.Ding
Ctx context.Context
Queries repository.Store
}
err = app.dig.Provide(func() utilityProvider {
return utilityProvider{
Log: app.log,
Config: &app.config,
Runtime: &app.runtime,
Ding: app.ding,
Ctx: app.ctx,
Queries: app.queries,
}
})
if err != nil {
return fmt.Errorf("failed to provide utilities to container: %w", err)
}
// services
err = app.setupServices()
@@ -260,7 +223,7 @@ func (app *BootstrapApp) Setup() error {
return configuredProviders[i].Name < configuredProviders[j].Name
})
if app.services.authService.LocalAuthConfigured() {
if app.services.AuthService.LocalAuthConfigured() {
configuredProviders = append(configuredProviders, model.Provider{
Name: "Local",
ID: "local",
@@ -268,7 +231,7 @@ func (app *BootstrapApp) Setup() error {
})
}
if app.services.authService.LDAPAuthConfigured() {
if app.services.AuthService.LDAPAuthConfigured() {
configuredProviders = append(configuredProviders, model.Provider{
Name: "LDAP",
ID: "ldap",
@@ -287,8 +250,8 @@ func (app *BootstrapApp) Setup() error {
app.runtime.ConfiguredProviders = configuredProviders
// throw in tailscale if it's configured just before setting up the controllers
if app.services.tailscaleService != nil {
app.runtime.TrustedDomains = append(app.runtime.TrustedDomains, "https://"+app.services.tailscaleService.GetHostname())
if app.services.TailscaleService != nil {
app.runtime.TrustedDomains = append(app.runtime.TrustedDomains, "https://"+app.services.TailscaleService.GetHostname())
}
// setup router
+21 -110
View File
@@ -13,7 +13,6 @@ import (
"github.com/tinyauthapp/tinyauth/internal/controller"
"github.com/tinyauthapp/tinyauth/internal/middleware"
"github.com/tinyauthapp/tinyauth/internal/model"
"go.uber.org/dig"
"github.com/gin-gonic/gin"
)
@@ -41,119 +40,31 @@ func (app *BootstrapApp) setupRouter() error {
}
}
err := app.dig.Provide(middleware.NewContextMiddleware)
contextMiddleware := middleware.NewContextMiddleware(app.log, app.runtime, app.services.AuthService, app.services.OAuthBrokerService, app.services.TailscaleService)
engine.Use(contextMiddleware.Middleware())
uiMiddleware, err := middleware.NewUIMiddleware()
if err != nil {
return fmt.Errorf("failed to provide context middleware: %w", err)
return fmt.Errorf("failed to initialize UI middleware: %w", err)
}
err = app.dig.Provide(middleware.NewUIMiddleware)
engine.Use(uiMiddleware.Middleware())
if err != nil {
return fmt.Errorf("failed to provide ui middleware: %w", err)
}
zerologMiddleware := middleware.NewZerologMiddleware(app.log)
err = app.dig.Provide(middleware.NewZerologMiddleware)
engine.Use(zerologMiddleware.Middleware())
if err != nil {
return fmt.Errorf("failed to provide zerolog middleware: %w", err)
}
apiRouter := engine.Group("/api")
type middlewareInput struct {
dig.In
ContextMiddleware *middleware.ContextMiddleware
UIMiddleware *middleware.UIMiddleware
ZerologMiddleware *middleware.ZerologMiddleware
}
err = app.dig.Invoke(func(mi middlewareInput) {
engine.Use(mi.ContextMiddleware.Middleware())
engine.Use(mi.UIMiddleware.Middleware())
engine.Use(mi.ZerologMiddleware.Middleware())
})
if err != nil {
return fmt.Errorf("failed to invoke middleware: %w", err)
}
err = app.dig.Provide(func() *gin.RouterGroup {
return &engine.RouterGroup
}, dig.Name("mainRouterGroup"))
if err != nil {
return fmt.Errorf("failed to provide main router group: %w", err)
}
err = app.dig.Provide(func() *gin.RouterGroup {
return engine.Group("/api")
}, dig.Name("apiRouterGroup"))
if err != nil {
return fmt.Errorf("failed to provide api router group: %w", err)
}
err = app.dig.Provide(controller.NewContextController)
if err != nil {
return fmt.Errorf("failed to provide context controller: %w", err)
}
err = app.dig.Provide(controller.NewOAuthController)
if err != nil {
return fmt.Errorf("failed to provide oauth controller: %w", err)
}
err = app.dig.Provide(controller.NewOIDCController)
if err != nil {
return fmt.Errorf("failed to provide oidc controller: %w", err)
}
err = app.dig.Provide(controller.NewProxyController)
if err != nil {
return fmt.Errorf("failed to provide proxy controller: %w", err)
}
err = app.dig.Provide(controller.NewUserController)
if err != nil {
return fmt.Errorf("failed to provide user controller: %w", err)
}
err = app.dig.Provide(controller.NewResourcesController)
if err != nil {
return fmt.Errorf("failed to provide resources controller: %w", err)
}
err = app.dig.Provide(controller.NewHealthController)
if err != nil {
return fmt.Errorf("failed to provide health controller: %w", err)
}
err = app.dig.Provide(controller.NewWellKnownController)
if err != nil {
return fmt.Errorf("failed to provide well-known controller: %w", err)
}
type controllerInput struct {
dig.In
ContextController *controller.ContextController
OAuthController *controller.OAuthController
OIDCController *controller.OIDCController
ProxyController *controller.ProxyController
UserController *controller.UserController
ResourcesController *controller.ResourcesController
HealthController *controller.HealthController
WellKnownController *controller.WellKnownController
}
// force dig to build all controllers and register their routes
err = app.dig.Invoke(func(ci controllerInput) error {
return nil
})
if err != nil {
return fmt.Errorf("failed to invoke controllers: %w", err)
}
controller.NewContextController(app.log, app.config, app.runtime, apiRouter)
controller.NewOAuthController(app.log, app.config, app.runtime, apiRouter, app.services.AuthService)
controller.NewOIDCController(app.log, app.services.OIDCService, app.runtime, apiRouter, &engine.RouterGroup)
controller.NewProxyController(app.log, app.runtime, apiRouter, app.services.AccessControlService, app.services.AuthService, app.services.PolicyEngine)
controller.NewUserController(app.log, app.runtime, apiRouter, app.services.AuthService)
controller.NewResourcesController(app.config, &engine.RouterGroup)
controller.NewHealthController(apiRouter)
controller.NewWellKnownController(app.services.OIDCService, &engine.RouterGroup)
app.router = engine
return nil
@@ -188,7 +99,7 @@ func (app *BootstrapApp) calculateListenerPolicy() []Listener {
l := []Listener{}
if !app.config.Server.ConcurrentListenersEnabled {
if app.services.tailscaleService != nil {
if app.services.TailscaleService != nil {
l = append(l, ListenerTailscale)
return l
}
@@ -206,7 +117,7 @@ func (app *BootstrapApp) calculateListenerPolicy() []Listener {
l = append(l, ListenerUnix)
}
if app.services.tailscaleService != nil {
if app.services.TailscaleService != nil {
l = append(l, ListenerTailscale)
}
@@ -275,9 +186,9 @@ func (app *BootstrapApp) serveUnix(ctx context.Context) error {
}
func (app *BootstrapApp) serveTailscale(ctx context.Context) error {
app.log.App.Info().Msgf("Starting Tailscale server on %s", fmt.Sprintf("https://%s", app.services.tailscaleService.GetHostname()))
app.log.App.Info().Msgf("Starting Tailscale server on %s", fmt.Sprintf("https://%s", app.services.TailscaleService.GetHostname()))
listener, err := app.services.tailscaleService.CreateListener()
listener, err := app.services.TailscaleService.CreateListener()
if err != nil {
return fmt.Errorf("failed to create tailscale listener: %w", err)
+70 -115
View File
@@ -5,84 +5,66 @@ import (
"os"
"github.com/tinyauthapp/tinyauth/internal/service"
"go.uber.org/dig"
)
func (app *BootstrapApp) setupServices() error {
err := app.setupPolicyEngine()
app.deps.service = &service.ServiceDependencies{
Log: app.log,
StaticConfig: &app.config,
RuntimeConfig: &app.runtime,
Ctx: app.ctx,
Ding: app.ding,
Services: &app.services,
Queries: &app.queries,
}
ldap, err := service.NewLdapService(app.deps.service)
if err != nil {
return fmt.Errorf("failed to setup policy engine: %w", err)
app.log.App.Warn().Err(err).Msg("Failed to initialize LDAP connection, will continue without it")
}
app.services.LDAPService = ldap
labelProvider, err := app.getLabelProvider()
if err != nil {
return fmt.Errorf("failed to get label provider: %w", err)
return fmt.Errorf("failed to initialize label provider: %w", err)
}
err = app.dig.Provide(func() service.LabelProvider {
return labelProvider
})
app.deps.service.LabelProvider = labelProvider
tailscaleService, err := service.NewTailscaleService(app.deps.service)
if err != nil {
return fmt.Errorf("failed to provide label provider: %w", err)
app.log.App.Warn().Err(err).Msg("Failed to initialize Tailscale connection, will continue without it")
}
err = app.dig.Provide(service.NewLdapService)
if err != nil {
return fmt.Errorf("failed to provide ldap service: %w", err)
}
app.services.TailscaleService = tailscaleService
err = app.dig.Provide(service.NewTailscaleService)
if err != nil {
return fmt.Errorf("failed to provide tailscale service: %w", err)
}
accessControlsService := service.NewAccessControlsService(app.deps.service)
app.services.AccessControlService = accessControlsService
err = app.dig.Provide(service.NewAccessControlsService)
if err != nil {
return fmt.Errorf("failed to provide access controls service: %w", err)
}
err = app.dig.Provide(service.NewOAuthBrokerService)
if err != nil {
return fmt.Errorf("failed to provide oauth broker service: %w", err)
}
err = app.dig.Provide(service.NewAuthService)
if err != nil {
return fmt.Errorf("failed to provide auth service: %w", err)
}
err = app.dig.Provide(service.NewOIDCService)
if err != nil {
return fmt.Errorf("failed to provide oidc service: %w", err)
}
type svcInput struct {
dig.In
AccessControlService *service.AccessControlsService
AuthService *service.AuthService
LDAPService *service.LdapService
OAuthBrokerService *service.OAuthBrokerService
OIDCService *service.OIDCService
TailscaleService *service.TailscaleService
}
err = app.dig.Invoke(func(i svcInput) error {
app.services.accessControlService = i.AccessControlService
app.services.authService = i.AuthService
app.services.ldapService = i.LDAPService
app.services.oauthBrokerService = i.OAuthBrokerService
app.services.tailscaleService = i.TailscaleService
return nil
})
err = app.setupPolicyEngine()
if err != nil {
return fmt.Errorf("failed to invoke services: %w", err)
return fmt.Errorf("failed to initialize policy engine: %w", err)
}
oauthBrokerService := service.NewOAuthBrokerService(app.deps.service)
app.services.OAuthBrokerService = oauthBrokerService
authService := service.NewAuthService(app.deps.service)
app.services.AuthService = authService
oidcService, err := service.NewOIDCService(app.deps.service)
if err != nil {
return fmt.Errorf("failed to initialize oidc service: %w", err)
}
app.services.OIDCService = oidcService
return nil
}
@@ -99,93 +81,66 @@ func (app *BootstrapApp) getLabelProvider() (service.LabelProvider, error) {
if useKubernetes {
app.log.App.Debug().Msg("Using Kubernetes label provider")
err := app.dig.Provide(service.NewKubernetesService)
kubernetesService, err := service.NewKubernetesService(app.deps.service)
if err != nil {
return nil, fmt.Errorf("failed to provide kubernetes service: %w", err)
return nil, fmt.Errorf("failed to initialize kubernetes service: %w", err)
}
err = app.dig.Invoke(func(k *service.KubernetesService) error {
app.services.kubernetesService = k
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to invoke kubernetes service: %w", err)
}
// Kubernetes will fail to initialize with an error if it cannot connect to the cluster
// but just to be safe, we check if the service is nil and log a warning if it is
if app.services.kubernetesService == nil {
if app.config.LabelProvider == "kubernetes" {
app.log.App.Warn().Msg("Kubernetes label provider selected but Kubernetes is not available, will continue without it")
}
return nil, nil
}
return app.services.kubernetesService, nil
app.services.KubernetesService = kubernetesService
return kubernetesService, nil
}
app.log.App.Debug().Msg("Using Docker label provider")
err := app.dig.Provide(service.NewDockerService)
dockerService, err := service.NewDockerService(app.deps.service)
if err != nil {
return nil, fmt.Errorf("failed to provide docker service: %w", err)
return nil, fmt.Errorf("failed to initialize docker service: %w", err)
}
err = app.dig.Invoke(func(d *service.DockerService) error {
app.services.dockerService = d
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to invoke docker service: %w", err)
}
if app.services.dockerService == nil {
if dockerService == nil {
if app.config.LabelProvider == "docker" {
app.log.App.Warn().Msg("Docker label provider selected but Docker is not available, will continue without it")
}
return nil, nil
}
return app.services.dockerService, nil
app.services.DockerService = dockerService
return dockerService, nil
default:
return nil, fmt.Errorf("invalid label provider: %s", app.config.LabelProvider)
}
}
func (app *BootstrapApp) setupPolicyEngine() error {
err := app.dig.Provide(service.NewPolicyEngine)
policyEngine, err := service.NewPolicyEngine(app.deps.service)
if err != nil {
return fmt.Errorf("failed to create policy engine: %w", err)
return fmt.Errorf("failed to initialize policy engine: %w", err)
}
err = app.dig.Invoke(func(policyEngine *service.PolicyEngine) error {
policyEngine.RegisterRule(service.RuleUserAllowed, &service.UserAllowedRule{
Log: app.log,
})
policyEngine.RegisterRule(service.RuleOAuthGroup, &service.OAuthGroupRule{
Log: app.log,
})
policyEngine.RegisterRule(service.RuleLDAPGroup, &service.LDAPGroupRule{
Log: app.log,
})
policyEngine.RegisterRule(service.RuleAuthEnabled, &service.AuthEnabledRule{
Log: app.log,
})
policyEngine.RegisterRule(service.RuleIPAllowed, &service.IPAllowedRule{
Log: app.log,
Config: app.config,
})
policyEngine.RegisterRule(service.RuleIPBypassed, &service.IPBypassedRule{
Log: app.log,
Config: app.config,
})
return nil
policyEngine.RegisterRule(service.RuleUserAllowed, &service.UserAllowedRule{
Log: app.log,
})
policyEngine.RegisterRule(service.RuleOAuthGroup, &service.OAuthGroupRule{
Log: app.log,
})
policyEngine.RegisterRule(service.RuleLDAPGroup, &service.LDAPGroupRule{
Log: app.log,
})
policyEngine.RegisterRule(service.RuleAuthEnabled, &service.AuthEnabledRule{
Log: app.log,
})
policyEngine.RegisterRule(service.RuleIPAllowed, &service.IPAllowedRule{
Log: app.log,
Config: app.config,
})
policyEngine.RegisterRule(service.RuleIPBypassed, &service.IPBypassedRule{
Log: app.log,
Config: app.config,
})
return err
app.services.PolicyEngine = policyEngine
return nil
}
+14 -19
View File
@@ -3,7 +3,6 @@ package controller
import (
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
"github.com/gin-gonic/gin"
)
@@ -72,33 +71,29 @@ type AppContextResponse struct {
App ACRApp `json:"app"`
}
type ContextControllerInput struct {
dig.In
Log *logger.Logger
Config *model.Config
Runtime *model.RuntimeConfig
RouterGroup *gin.RouterGroup `name:"apiRouterGroup"`
}
type ContextController struct {
log *logger.Logger
config *model.Config
runtime *model.RuntimeConfig
config model.Config
runtime model.RuntimeConfig
}
func NewContextController(i ContextControllerInput) *ContextController {
func NewContextController(
log *logger.Logger,
config model.Config,
runtimeConfig model.RuntimeConfig,
router *gin.RouterGroup,
) *ContextController {
controller := &ContextController{
log: i.Log,
config: i.Config,
runtime: i.Runtime,
log: log,
config: config,
runtime: runtimeConfig,
}
if !i.Config.UI.WarningsEnabled {
i.Log.App.Warn().Msg("UI warnings are disabled. This may lead to security issues if you are not careful. Make sure to enable warnings in production environments.")
if !config.UI.WarningsEnabled {
log.App.Warn().Msg("UI warnings are disabled. This may lead to security issues if you are not careful. Make sure to enable warnings in production environments.")
}
contextGroup := i.RouterGroup.Group("/context")
contextGroup := router.Group("/context")
contextGroup.GET("/user", controller.userContextHandler)
contextGroup.GET("/app", controller.appContextHandler)
@@ -121,12 +121,7 @@ func TestContextController(t *testing.T) {
group := router.Group("/api")
gin.SetMode(gin.TestMode)
controller.NewContextController(controller.ContextControllerInput{
Log: log,
Config: &cfg,
Runtime: &runtime,
RouterGroup: group,
})
controller.NewContextController(log, cfg, runtime, group)
recorder := httptest.NewRecorder()
+4 -13
View File
@@ -1,24 +1,15 @@
package controller
import (
"github.com/gin-gonic/gin"
"go.uber.org/dig"
)
import "github.com/gin-gonic/gin"
type HealthController struct {
}
type HealthControllerInput struct {
dig.In
RouterGroup *gin.RouterGroup `name:"apiRouterGroup"`
}
func NewHealthController(i HealthControllerInput) *HealthController {
func NewHealthController(router *gin.RouterGroup) *HealthController {
controller := &HealthController{}
i.RouterGroup.GET("/healthz", controller.healthHandler)
i.RouterGroup.HEAD("/healthz", controller.healthHandler)
router.GET("/healthz", controller.healthHandler)
router.HEAD("/healthz", controller.healthHandler)
return controller
}
@@ -55,9 +55,7 @@ func TestHealthController(t *testing.T) {
group := router.Group("/api")
gin.SetMode(gin.TestMode)
controller.NewHealthController(controller.HealthControllerInput{
RouterGroup: group,
})
controller.NewHealthController(group)
recorder := httptest.NewRecorder()
+14 -19
View File
@@ -11,7 +11,6 @@ import (
"github.com/tinyauthapp/tinyauth/internal/service"
"github.com/tinyauthapp/tinyauth/internal/utils"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
"github.com/gin-gonic/gin"
"github.com/google/go-querystring/query"
@@ -23,30 +22,26 @@ type OAuthRequest struct {
type OAuthController struct {
log *logger.Logger
config *model.Config
runtime *model.RuntimeConfig
config model.Config
runtime model.RuntimeConfig
auth *service.AuthService
}
type OAuthControllerInput struct {
dig.In
Log *logger.Logger
Config *model.Config
RuntimeConfig *model.RuntimeConfig
RouterGroup *gin.RouterGroup `name:"apiRouterGroup"`
AuthService *service.AuthService
}
func NewOAuthController(i OAuthControllerInput) *OAuthController {
func NewOAuthController(
log *logger.Logger,
config model.Config,
runtimeConfig model.RuntimeConfig,
router *gin.RouterGroup,
auth *service.AuthService,
) *OAuthController {
controller := &OAuthController{
log: i.Log,
config: i.Config,
runtime: i.RuntimeConfig,
auth: i.AuthService,
log: log,
config: config,
runtime: runtimeConfig,
auth: auth,
}
oauthGroup := i.RouterGroup.Group("/oauth")
oauthGroup := router.Group("/oauth")
oauthGroup.GET("/url/:provider", controller.oauthURLHandler)
oauthGroup.GET("/callback/:provider", controller.oauthCallbackHandler)
+13 -19
View File
@@ -11,7 +11,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/google/go-querystring/query"
"go.uber.org/dig"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/service"
@@ -31,7 +30,7 @@ type authorizeErrorParams struct {
type OIDCController struct {
log *logger.Logger
oidc *service.OIDCService
runtime *model.RuntimeConfig
runtime model.RuntimeConfig
}
type AuthorizeCallback struct {
@@ -79,27 +78,22 @@ type AuthorizeCompleteRequest struct {
Ticket string `json:"ticket" binding:"required"`
}
type OIDCControllerInput struct {
dig.In
Log *logger.Logger
OIDCService *service.OIDCService
RuntimeConfig *model.RuntimeConfig
RouterGroup *gin.RouterGroup `name:"apiRouterGroup"`
MainRouter *gin.RouterGroup `name:"mainRouterGroup"`
}
func NewOIDCController(i OIDCControllerInput) *OIDCController {
func NewOIDCController(
log *logger.Logger,
oidcService *service.OIDCService,
runtimeConfig model.RuntimeConfig,
router *gin.RouterGroup,
mainRouter *gin.RouterGroup) *OIDCController {
controller := &OIDCController{
log: i.Log,
oidc: i.OIDCService,
runtime: i.RuntimeConfig,
log: log,
oidc: oidcService,
runtime: runtimeConfig,
}
i.MainRouter.POST("/authorize", controller.authorize)
i.MainRouter.GET("/authorize", controller.authorize)
mainRouter.POST("/authorize", controller.authorize)
mainRouter.GET("/authorize", controller.authorize)
oidcGroup := i.RouterGroup.Group("/oidc")
oidcGroup := router.Group("/oidc")
oidcGroup.POST("/authorize-complete", controller.authorizeComplete)
oidcGroup.POST("/token", controller.Token)
oidcGroup.GET("/userinfo", controller.Userinfo)
+2 -14
View File
@@ -35,13 +35,7 @@ func TestOIDCController(t *testing.T) {
store := memory.New()
oidcService, err := service.NewOIDCService(service.OIDCServiceInput{
Log: log,
Config: &cfg,
Runtime: &runtime,
Queries: store,
Ding: dg,
})
oidcService, err := service.NewOIDCService(log, cfg, runtime, store, dg)
require.NoError(t, err)
// Middleware that injects an authenticated local user into the gin context,
@@ -837,13 +831,7 @@ func TestOIDCController(t *testing.T) {
svc = nil
}
controller.NewOIDCController(controller.OIDCControllerInput{
Log: log,
OIDCService: svc,
RuntimeConfig: &runtime,
RouterGroup: group,
MainRouter: &router.RouterGroup,
})
controller.NewOIDCController(log, svc, runtime, group, &router.RouterGroup)
recorder := httptest.NewRecorder()
+15 -20
View File
@@ -13,7 +13,6 @@ import (
"github.com/tinyauthapp/tinyauth/internal/service"
"github.com/tinyauthapp/tinyauth/internal/utils"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
"github.com/gin-gonic/gin"
"github.com/google/go-querystring/query"
@@ -54,33 +53,29 @@ type ProxyContext struct {
type ProxyController struct {
log *logger.Logger
runtime *model.RuntimeConfig
runtime model.RuntimeConfig
acls *service.AccessControlsService
auth *service.AuthService
policyEngine *service.PolicyEngine
}
type ProxyControllerInput struct {
dig.In
Log *logger.Logger
RuntimeConfig *model.RuntimeConfig
RouterGroup *gin.RouterGroup `name:"apiRouterGroup"`
ACLsService *service.AccessControlsService
AuthService *service.AuthService
PolicyEngine *service.PolicyEngine
}
func NewProxyController(i ProxyControllerInput) *ProxyController {
func NewProxyController(
log *logger.Logger,
runtime model.RuntimeConfig,
router *gin.RouterGroup,
acls *service.AccessControlsService,
auth *service.AuthService,
policyEngine *service.PolicyEngine,
) *ProxyController {
controller := &ProxyController{
log: i.Log,
runtime: i.RuntimeConfig,
acls: i.ACLsService,
auth: i.AuthService,
policyEngine: i.PolicyEngine,
log: log,
runtime: runtime,
acls: acls,
auth: auth,
policyEngine: policyEngine,
}
proxyGroup := i.RouterGroup.Group("/auth")
proxyGroup := router.Group("/auth")
proxyGroup.Any("/:proxy", controller.proxyHandler)
return controller
+5 -34
View File
@@ -369,21 +369,10 @@ func TestProxyController(t *testing.T) {
ctx := context.TODO()
dg := ding.New(ctx)
broker := service.NewOAuthBrokerService(service.OAuthBrokerServiceInput{
Log: log,
Runtime: &runtime,
Ctx: ctx,
})
aclsService := service.NewAccessControlsService(service.AccessControlServiceInput{
Log: log,
Config: &cfg,
LabelProvider: nil,
})
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
aclsService := service.NewAccessControlsService(log, cfg, nil)
policyEngine, err := service.NewPolicyEngine(service.PolicyEngineInput{
Log: log,
Config: &cfg,
})
policyEngine, err := service.NewPolicyEngine(cfg, log)
require.NoError(t, err)
policyEngine.RegisterRule(service.RuleUserAllowed, &service.UserAllowedRule{
@@ -406,18 +395,7 @@ func TestProxyController(t *testing.T) {
Log: log,
})
authService := service.NewAuthService(service.AuthServiceInput{
Log: log,
Config: &cfg,
Runtime: &runtime,
Ctx: ctx,
Ding: dg,
LDAP: nil,
Queries: store,
OAuthBroker: broker,
Tailscale: nil,
PolicyEngine: policyEngine,
})
authService := service.NewAuthService(log, cfg, runtime, ctx, dg, nil, store, broker, nil, policyEngine)
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
@@ -432,14 +410,7 @@ func TestProxyController(t *testing.T) {
recorder := httptest.NewRecorder()
controller.NewProxyController(controller.ProxyControllerInput{
Log: log,
RuntimeConfig: &runtime,
RouterGroup: group,
ACLsService: aclsService,
AuthService: authService,
PolicyEngine: policyEngine,
})
controller.NewProxyController(log, runtime, group, aclsService, authService, policyEngine)
test.run(t, router, recorder)
})
+8 -13
View File
@@ -5,30 +5,25 @@ import (
"github.com/gin-gonic/gin"
"github.com/tinyauthapp/tinyauth/internal/model"
"go.uber.org/dig"
)
type ResourcesController struct {
config *model.Config
config model.Config
fileServer http.Handler
}
type ResourcesControllerInput struct {
dig.In
RouterGroup *gin.RouterGroup `name:"mainRouterGroup"`
Config *model.Config
}
func NewResourcesController(i ResourcesControllerInput) *ResourcesController {
fileServer := http.StripPrefix("/resources", http.FileServer(http.Dir(i.Config.Resources.Path)))
func NewResourcesController(
config model.Config,
router *gin.RouterGroup,
) *ResourcesController {
fileServer := http.StripPrefix("/resources", http.FileServer(http.Dir(config.Resources.Path)))
controller := &ResourcesController{
config: i.Config,
config: config,
fileServer: fileServer,
}
i.RouterGroup.GET("/resources/*resource", controller.resourcesHandler)
router.GET("/resources/*resource", controller.resourcesHandler)
return controller
}
@@ -69,10 +69,7 @@ func TestResourcesController(t *testing.T) {
group := router.Group("/")
gin.SetMode(gin.TestMode)
controller.NewResourcesController(controller.ResourcesControllerInput{
RouterGroup: group,
Config: &cfg,
})
controller.NewResourcesController(cfg, group)
recorder := httptest.NewRecorder()
test.run(t, router, recorder)
+11 -16
View File
@@ -11,7 +11,6 @@ import (
"github.com/tinyauthapp/tinyauth/internal/service"
"github.com/tinyauthapp/tinyauth/internal/utils"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
"github.com/gin-gonic/gin"
"github.com/pquerna/otp/totp"
@@ -28,27 +27,23 @@ type TotpRequest struct {
type UserController struct {
log *logger.Logger
runtime *model.RuntimeConfig
runtime model.RuntimeConfig
auth *service.AuthService
}
type UserControllerInput struct {
dig.In
Log *logger.Logger
RuntimeConfig *model.RuntimeConfig
RouterGroup *gin.RouterGroup `name:"apiRouterGroup"`
AuthService *service.AuthService
}
func NewUserController(i UserControllerInput) *UserController {
func NewUserController(
log *logger.Logger,
runtimeConfig model.RuntimeConfig,
router *gin.RouterGroup,
auth *service.AuthService,
) *UserController {
controller := &UserController{
log: i.Log,
runtime: i.RuntimeConfig,
auth: i.AuthService,
log: log,
runtime: runtimeConfig,
auth: auth,
}
userGroup := i.RouterGroup.Group("/user")
userGroup := router.Group("/user")
userGroup.POST("/login", controller.loginHandler)
userGroup.POST("/logout", controller.logoutHandler)
userGroup.POST("/totp", controller.totpHandler)
+4 -27
View File
@@ -414,29 +414,11 @@ func TestUserController(t *testing.T) {
ctx := context.TODO()
dg := ding.New(ctx)
policyEngine, err := service.NewPolicyEngine(service.PolicyEngineInput{
Log: log,
Config: &cfg,
})
policyEngine, err := service.NewPolicyEngine(cfg, log)
require.NoError(t, err)
broker := service.NewOAuthBrokerService(service.OAuthBrokerServiceInput{
Log: log,
Runtime: &runtime,
Ctx: ctx,
})
authService := service.NewAuthService(service.AuthServiceInput{
Log: log,
Config: &cfg,
Runtime: &runtime,
Ctx: ctx,
Ding: dg,
LDAP: nil,
Queries: store,
OAuthBroker: broker,
Tailscale: nil,
PolicyEngine: policyEngine,
})
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
authService := service.NewAuthService(log, cfg, runtime, ctx, dg, nil, store, broker, nil, policyEngine)
beforeEach := func() {
// Clear failed login attempts before each test
@@ -455,12 +437,7 @@ func TestUserController(t *testing.T) {
group := router.Group("/api")
gin.SetMode(gin.TestMode)
controller.NewUserController(controller.UserControllerInput{
Log: log,
RuntimeConfig: &runtime,
RouterGroup: group,
AuthService: authService,
})
controller.NewUserController(log, runtime, group, authService)
recorder := httptest.NewRecorder()
+4 -12
View File
@@ -6,7 +6,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/tinyauthapp/tinyauth/internal/service"
"go.uber.org/dig"
)
type OpenIDConnectConfiguration struct {
@@ -31,20 +30,13 @@ type WellKnownController struct {
oidc *service.OIDCService
}
type WellKnownControllerInput struct {
dig.In
OIDCService *service.OIDCService
RouterGroup *gin.RouterGroup `name:"mainRouterGroup"`
}
func NewWellKnownController(i WellKnownControllerInput) *WellKnownController {
func NewWellKnownController(oidc *service.OIDCService, router *gin.RouterGroup) *WellKnownController {
controller := &WellKnownController{
oidc: i.OIDCService,
oidc: oidc,
}
i.RouterGroup.GET("/.well-known/openid-configuration", controller.OpenIDConnectConfiguration)
i.RouterGroup.GET("/.well-known/jwks.json", controller.JWKS)
router.GET("/.well-known/openid-configuration", controller.OpenIDConnectConfiguration)
router.GET("/.well-known/jwks.json", controller.JWKS)
return controller
}
@@ -93,13 +93,7 @@ func TestWellKnownController(t *testing.T) {
store := memory.New()
oidcService, err := service.NewOIDCService(service.OIDCServiceInput{
Log: log,
Config: &cfg,
Runtime: &runtime,
Queries: store,
Ding: dg,
})
oidcService, err := service.NewOIDCService(log, cfg, runtime, store, dg)
require.NoError(t, err)
for _, test := range tests {
@@ -109,10 +103,7 @@ func TestWellKnownController(t *testing.T) {
recorder := httptest.NewRecorder()
controller.NewWellKnownController(controller.WellKnownControllerInput{
OIDCService: oidcService,
RouterGroup: &router.RouterGroup,
})
controller.NewWellKnownController(oidcService, &router.RouterGroup)
test.run(t, router, recorder)
})
+13 -18
View File
@@ -11,7 +11,6 @@ import (
"github.com/tinyauthapp/tinyauth/internal/service"
"github.com/tinyauthapp/tinyauth/internal/utils"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
"github.com/gin-gonic/gin"
)
@@ -38,29 +37,25 @@ var (
type ContextMiddleware struct {
log *logger.Logger
runtime *model.RuntimeConfig
runtime model.RuntimeConfig
auth *service.AuthService
broker *service.OAuthBrokerService
tailscale *service.TailscaleService
}
type ContextMiddlewareInput struct {
dig.In
Log *logger.Logger
RuntimeConfig *model.RuntimeConfig
AuthService *service.AuthService
BrokerService *service.OAuthBrokerService
TailscaleService *service.TailscaleService
}
func NewContextMiddleware(i ContextMiddlewareInput) *ContextMiddleware {
func NewContextMiddleware(
log *logger.Logger,
runtime model.RuntimeConfig,
auth *service.AuthService,
broker *service.OAuthBrokerService,
tailscale *service.TailscaleService,
) *ContextMiddleware {
return &ContextMiddleware{
log: i.Log,
runtime: i.RuntimeConfig,
auth: i.AuthService,
broker: i.BrokerService,
tailscale: i.TailscaleService,
log: log,
runtime: runtime,
auth: auth,
broker: broker,
tailscale: tailscale,
}
}
+4 -28
View File
@@ -254,37 +254,13 @@ func TestContextMiddleware(t *testing.T) {
store := memory.New()
policyEngine, err := service.NewPolicyEngine(service.PolicyEngineInput{
Log: log,
Config: &cfg,
})
policyEngine, err := service.NewPolicyEngine(cfg, log)
require.NoError(t, err)
broker := service.NewOAuthBrokerService(service.OAuthBrokerServiceInput{
Log: log,
Runtime: &runtime,
Ctx: ctx,
})
authService := service.NewAuthService(service.AuthServiceInput{
Log: log,
Config: &cfg,
Runtime: &runtime,
Ctx: ctx,
Ding: dg,
LDAP: nil,
Queries: store,
OAuthBroker: broker,
Tailscale: nil,
PolicyEngine: policyEngine,
})
broker := service.NewOAuthBrokerService(log, map[string]model.OAuthServiceConfig{}, ctx)
authService := service.NewAuthService(log, cfg, runtime, ctx, dg, nil, store, broker, nil, policyEngine)
contextMiddleware := middleware.NewContextMiddleware(middleware.ContextMiddlewareInput{
Log: log,
RuntimeConfig: &runtime,
AuthService: authService,
BrokerService: broker,
TailscaleService: nil,
})
contextMiddleware := middleware.NewContextMiddleware(log, runtime, authService, broker, nil)
for _, test := range tests {
authService.ClearLoginAttempts()
+1 -7
View File
@@ -9,7 +9,6 @@ import (
"time"
"github.com/tinyauthapp/tinyauth/internal/assets"
"go.uber.org/dig"
"github.com/gin-gonic/gin"
)
@@ -19,12 +18,7 @@ type UIMiddleware struct {
uiFileServer http.Handler
}
// for future use if we need to inject dependencies into the middleware
type UIMiddlewareInput struct {
dig.In
}
func NewUIMiddleware(_ UIMiddlewareInput) (*UIMiddleware, error) {
func NewUIMiddleware() (*UIMiddleware, error) {
m := &UIMiddleware{}
ui, err := fs.Sub(assets.FrontendAssets, "dist")
+2 -9
View File
@@ -6,7 +6,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
)
// See context middleware for explanation of why we have to do this
@@ -22,15 +21,9 @@ type ZerologMiddleware struct {
log *logger.Logger
}
type ZerologMiddlewareInput struct {
dig.In
Log *logger.Logger
}
func NewZerologMiddleware(i ZerologMiddlewareInput) *ZerologMiddleware {
func NewZerologMiddleware(log *logger.Logger) *ZerologMiddleware {
return &ZerologMiddleware{
log: i.Log,
log: log,
}
}
+1
View File
@@ -12,6 +12,7 @@ type RuntimeConfig struct {
OAuthProviders map[string]OAuthServiceConfig
OAuthWhitelist []string
ConfiguredProviders []Provider
OIDCClients []OIDCClientConfig
TrustedDomains []string
}
+9 -16
View File
@@ -5,7 +5,6 @@ import (
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
)
type LabelProvider interface {
@@ -15,23 +14,17 @@ type LabelProvider interface {
type AccessControlsService struct {
log *logger.Logger
config *model.Config
labelProvider LabelProvider
labelProvider *LabelProvider
}
type AccessControlServiceInput struct {
dig.In
Log *logger.Logger
Config *model.Config
LabelProvider LabelProvider `optional:"true"`
}
func NewAccessControlsService(i AccessControlServiceInput) *AccessControlsService {
func NewAccessControlsService(
deps *ServiceDependencies,
) *AccessControlsService {
return &AccessControlsService{
log: i.Log,
config: i.Config,
labelProvider: i.LabelProvider,
log: deps.Log,
config: deps.StaticConfig,
labelProvider: &deps.LabelProvider,
}
}
@@ -63,8 +56,8 @@ func (service *AccessControlsService) GetAccessControls(domain string) (*model.A
}
// If we have a label provider configured, try to get ACLs from it
if service.labelProvider != nil {
return service.labelProvider.GetLabels(domain)
if service.labelProvider != nil && *service.labelProvider != nil {
return (*service.labelProvider).GetLabels(domain)
}
// no labels
@@ -87,11 +87,7 @@ func TestLookupStaticACLs(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
svc := NewAccessControlsService(AccessControlServiceInput{
Log: log,
Config: &model.Config{Apps: tt.apps},
LabelProvider: nil,
})
svc := NewAccessControlsService(log, model.Config{Apps: tt.apps}, nil)
got := svc.lookupStaticACLs(tt.domain)
if tt.expectNil {
assert.Nil(t, got)
@@ -116,11 +112,7 @@ func TestGetAccessControls(t *testing.T) {
},
},
}
svc := NewAccessControlsService(AccessControlServiceInput{
Log: log,
Config: &config,
LabelProvider: nil,
})
svc := NewAccessControlsService(log, config, nil)
got, err := svc.GetAccessControls("foo.example.com")
@@ -131,11 +123,7 @@ func TestGetAccessControls(t *testing.T) {
})
t.Run("returns nil when no static match and no label provider", func(t *testing.T) {
svc := NewAccessControlsService(AccessControlServiceInput{
Log: log,
Config: &model.Config{},
LabelProvider: nil,
})
svc := NewAccessControlsService(log, model.Config{}, nil)
got, err := svc.GetAccessControls("unknown.example.com")
@@ -145,11 +133,7 @@ func TestGetAccessControls(t *testing.T) {
t.Run("returns nil when label provider pointer wraps a nil interface", func(t *testing.T) {
var provider LabelProvider
svc := NewAccessControlsService(AccessControlServiceInput{
Log: log,
Config: &model.Config{},
LabelProvider: provider, // nil provider
})
svc := NewAccessControlsService(log, model.Config{}, &provider)
got, err := svc.GetAccessControls("unknown.example.com")
@@ -168,11 +152,7 @@ func TestGetAccessControls(t *testing.T) {
},
}
var provider LabelProvider = mock
svc := NewAccessControlsService(AccessControlServiceInput{
Log: log,
Config: &model.Config{},
LabelProvider: provider,
})
svc := NewAccessControlsService(log, model.Config{}, &provider)
got, err := svc.GetAccessControls("dynamic.example.com")
@@ -190,11 +170,7 @@ func TestGetAccessControls(t *testing.T) {
"foo": {Config: model.AppConfig{Domain: "foo.example.com"}},
},
}
svc := NewAccessControlsService(AccessControlServiceInput{
Log: log,
Config: &config,
LabelProvider: provider,
})
svc := NewAccessControlsService(log, config, &provider)
got, err := svc.GetAccessControls("foo.example.com")
@@ -212,11 +188,7 @@ func TestGetAccessControls(t *testing.T) {
},
}
var provider LabelProvider = mock
svc := NewAccessControlsService(AccessControlServiceInput{
Log: log,
Config: &model.Config{},
LabelProvider: provider,
})
svc := NewAccessControlsService(log, model.Config{}, &provider)
got, err := svc.GetAccessControls("dynamic.example.com")
+13 -27
View File
@@ -14,7 +14,6 @@ import (
"github.com/tinyauthapp/tinyauth/internal/repository"
"github.com/tinyauthapp/tinyauth/internal/utils"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
@@ -83,32 +82,19 @@ type AuthService struct {
}
}
type AuthServiceInput struct {
dig.In
Log *logger.Logger
Config *model.Config
Runtime *model.RuntimeConfig
Ctx context.Context
Ding *ding.Ding
LDAP *LdapService `optional:"true"`
Queries repository.Store
OAuthBroker *OAuthBrokerService
Tailscale *TailscaleService `optional:"true"`
PolicyEngine *PolicyEngine
}
func NewAuthService(i AuthServiceInput) *AuthService {
func NewAuthService(
deps *ServiceDependencies,
) *AuthService {
service := &AuthService{
log: i.Log,
runtime: i.Runtime,
ctx: i.Ctx,
config: i.Config,
ldap: i.LDAP,
queries: i.Queries,
oauthBroker: i.OAuthBroker,
tailscale: i.Tailscale,
policyEngine: i.PolicyEngine,
log: deps.Log,
runtime: deps.RuntimeConfig,
ctx: deps.Ctx,
config: deps.StaticConfig,
ldap: deps.Services.LDAPService,
queries: *deps.Queries,
oauthBroker: deps.Services.OAuthBrokerService,
tailscale: deps.Services.TailscaleService,
policyEngine: deps.Services.PolicyEngine,
}
// caches setup
@@ -120,7 +106,7 @@ func NewAuthService(i AuthServiceInput) *AuthService {
service.caches.login = loginCache
service.caches.ldap = ldapCache
i.Ding.Go(func(ctx context.Context) {
deps.Ding.Go(func(ctx context.Context) {
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
+1 -16
View File
@@ -4,7 +4,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
)
@@ -13,22 +12,9 @@ func TestIsEmailWhitelistedUsesProviderSpecificList(t *testing.T) {
log := logger.NewLogger().WithTestConfig()
log.Init()
policyEngine, err := NewPolicyEngine(PolicyEngineInput{
Log: log,
Config: &model.Config{
Auth: model.AuthConfig{
ACLs: model.ACLsConfig{
Policy: string(PolicyAllow),
},
},
},
})
require.NoError(t, err)
auth := &AuthService{
log: log,
runtime: &model.RuntimeConfig{
runtime: model.RuntimeConfig{
OAuthWhitelist: []string{"global@example.com"},
OAuthProviders: map[string]model.OAuthServiceConfig{
"github": {
@@ -42,7 +28,6 @@ func TestIsEmailWhitelistedUsesProviderSpecificList(t *testing.T) {
},
},
},
policyEngine: policyEngine,
}
assert.True(t, auth.IsEmailWhitelisted("github", "github@example.com"))
+9 -16
View File
@@ -8,7 +8,6 @@ import (
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils/decoders"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
container "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
@@ -22,40 +21,34 @@ type DockerService struct {
isConnected bool
}
type DockerServiceInput struct {
dig.In
Log *logger.Logger
Ctx context.Context
Ding *ding.Ding
}
func NewDockerService(i DockerServiceInput) (*DockerService, error) {
func NewDockerService(
deps *ServiceDependencies,
) (*DockerService, error) {
client, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return nil, err
}
client.NegotiateAPIVersion(i.Ctx)
client.NegotiateAPIVersion(deps.Ctx)
_, err = client.Ping(i.Ctx)
_, err = client.Ping(deps.Ctx)
if err != nil {
i.Log.App.Debug().Err(err).Msg("Docker not connected")
deps.Log.App.Debug().Err(err).Msg("Docker not connected")
return nil, nil
}
service := &DockerService{
log: i.Log,
log: deps.Log,
client: client,
context: i.Ctx,
context: deps.Ctx,
}
service.isConnected = true
service.log.App.Debug().Msg("Docker connected successfully")
i.Ding.Go(service.watchAndClose, ding.RingMajor)
deps.Ding.Go(service.watchAndClose, ding.RingMajor)
return service, nil
}
+9 -16
View File
@@ -12,7 +12,6 @@ import (
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils/decoders"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -49,15 +48,9 @@ type KubernetesService struct {
appNameIndex map[string]ingressAppKey
}
type KubernetesServiceInput struct {
dig.In
Log *logger.Logger
Ctx context.Context
Ding *ding.Ding
}
func NewKubernetesService(i KubernetesServiceInput) (*KubernetesService, error) {
func NewKubernetesService(
deps *ServiceDependencies,
) (*KubernetesService, error) {
cfg, err := rest.InClusterConfig()
if err != nil {
return nil, fmt.Errorf("failed to get in-cluster kubernetes config: %w", err)
@@ -74,31 +67,31 @@ func NewKubernetesService(i KubernetesServiceInput) (*KubernetesService, error)
Resource: "ingresses",
}
accessCtx, accessCancel := context.WithTimeout(i.Ctx, 5*time.Second)
accessCtx, accessCancel := context.WithTimeout(deps.Ctx, 5*time.Second)
defer accessCancel()
_, err = client.Resource(gvr).List(accessCtx, metav1.ListOptions{Limit: 1})
if err != nil {
i.Log.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Failed to access Ingress API, Kubernetes label provider will be disabled")
deps.Log.App.Warn().Err(err).Str("api", gvr.GroupVersion().String()).Msg("Failed to access Ingress API, Kubernetes label provider will be disabled")
return nil, fmt.Errorf("failed to access ingress api: %w", err)
}
i.Log.App.Debug().Str("api", gvr.GroupVersion().String()).Msg("Successfully accessed Ingress API, starting watcher")
deps.Log.App.Debug().Str("api", gvr.GroupVersion().String()).Msg("Successfully accessed Ingress API, starting watcher")
service := &KubernetesService{
log: i.Log,
log: deps.Log,
client: client,
ingressApps: make(map[ingressKey][]ingressApp),
domainIndex: make(map[string]ingressAppKey),
appNameIndex: make(map[string]ingressAppKey),
}
i.Ding.Go(func(ctx context.Context) {
deps.Ding.Go(func(ctx context.Context) {
service.watchGVR(gvr, ctx)
}, ding.RingMajor)
service.started = true
i.Log.App.Debug().Msg("Kubernetes label provider started successfully")
deps.Log.App.Debug().Msg("Kubernetes label provider started successfully")
return service, nil
}
+18 -24
View File
@@ -13,48 +13,42 @@ import (
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
)
type LdapService struct {
log *logger.Logger
config *model.Config
conn *ldapgo.Conn
mutex sync.RWMutex
cert *tls.Certificate
bindPw string
conn *ldapgo.Conn
mutex sync.RWMutex
cert *tls.Certificate
ldapBindPw string
}
type LdapServiceInput struct {
dig.In
Log *logger.Logger
Config *model.Config
Ding *ding.Ding
}
func NewLdapService(i LdapServiceInput) (*LdapService, error) {
if i.Config.LDAP.Address == "" {
func NewLdapService(
deps *ServiceDependencies,
) (*LdapService, error) {
if deps.StaticConfig.LDAP.Address == "" {
return nil, nil
}
ldapBindPw := utils.GetSecret(deps.StaticConfig.LDAP.BindPassword, deps.StaticConfig.LDAP.BindPasswordFile)
ldap := &LdapService{
log: i.Log,
config: i.Config,
log: deps.Log,
config: deps.StaticConfig,
ldapBindPw: ldapBindPw,
}
ldap.bindPw = utils.GetSecret(i.Config.LDAP.BindPassword, i.Config.LDAP.BindPasswordFile)
// Check whether authentication with client certificate is possible
if i.Config.LDAP.AuthCert != "" && i.Config.LDAP.AuthKey != "" {
cert, err := tls.LoadX509KeyPair(i.Config.LDAP.AuthCert, i.Config.LDAP.AuthKey)
if deps.StaticConfig.LDAP.AuthCert != "" && deps.StaticConfig.LDAP.AuthKey != "" {
cert, err := tls.LoadX509KeyPair(deps.StaticConfig.LDAP.AuthCert, deps.StaticConfig.LDAP.AuthKey)
if err != nil {
return nil, fmt.Errorf("failed to initialize LDAP with mTLS authentication: %w", err)
}
i.Log.App.Info().Msg("LDAP mTLS authentication configured successfully")
ldap.log.App.Info().Msg("LDAP mTLS authentication configured successfully")
ldap.cert = &cert
@@ -76,7 +70,7 @@ func NewLdapService(i LdapServiceInput) (*LdapService, error) {
return nil, fmt.Errorf("failed to connect to ldap server: %w", err)
}
i.Ding.Go(func(ctx context.Context) {
deps.Ding.Go(func(ctx context.Context) {
ldap.log.App.Debug().Msg("Starting LDAP connection heartbeat routine")
ticker := time.NewTicker(5 * time.Minute)
@@ -221,7 +215,7 @@ func (ldap *LdapService) BindService(rebind bool) error {
if ldap.cert != nil {
return ldap.conn.ExternalBind()
}
return ldap.conn.Bind(ldap.config.LDAP.BindDN, ldap.bindPw)
return ldap.conn.Bind(ldap.config.LDAP.BindDN, ldap.config.LDAP.BindPassword)
}
func (ldap *LdapService) Bind(userDN string, password string) error {
+7 -14
View File
@@ -5,7 +5,6 @@ import (
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
"slices"
@@ -33,27 +32,21 @@ var presets = map[string]func(config model.OAuthServiceConfig, ctx context.Conte
"google": newGoogleOAuthService,
}
type OAuthBrokerServiceInput struct {
dig.In
Log *logger.Logger
Runtime *model.RuntimeConfig
Ctx context.Context
}
func NewOAuthBrokerService(i OAuthBrokerServiceInput) *OAuthBrokerService {
func NewOAuthBrokerService(
deps *ServiceDependencies,
) *OAuthBrokerService {
service := &OAuthBrokerService{
log: i.Log,
log: deps.Log,
services: make(map[string]OAuthServiceImpl),
configs: i.Runtime.OAuthProviders,
configs: deps.RuntimeConfig.OAuthProviders,
}
for name, cfg := range service.configs {
if presetFunc, exists := presets[name]; exists {
service.services[name] = presetFunc(cfg, i.Ctx)
service.services[name] = presetFunc(cfg, deps.Ctx)
service.log.App.Debug().Str("service", name).Msg("Loaded OAuth service from preset")
} else {
service.services[name] = NewOAuthService(cfg, name, i.Ctx)
service.services[name] = NewOAuthService(cfg, name, deps.Ctx)
service.log.App.Debug().Str("service", name).Msg("Loaded OAuth service from custom config")
}
}
+23 -41
View File
@@ -14,7 +14,6 @@ import (
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
"time"
@@ -27,7 +26,6 @@ import (
"github.com/tinyauthapp/tinyauth/internal/repository"
"github.com/tinyauthapp/tinyauth/internal/utils"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
)
var (
@@ -151,24 +149,16 @@ type OIDCService struct {
}
}
type OIDCServiceInput struct {
dig.In
Log *logger.Logger
Config *model.Config
Runtime *model.RuntimeConfig
Queries repository.Store
Ding *ding.Ding
}
func NewOIDCService(i OIDCServiceInput) (*OIDCService, error) {
func NewOIDCService(
deps *ServiceDependencies,
) (*OIDCService, error) {
// If not configured, skip init
if len(i.Config.OIDC.Clients) == 0 {
if len(deps.RuntimeConfig.OIDCClients) == 0 {
return nil, nil
}
// Ensure issuer is https
uissuer, err := url.Parse(i.Runtime.AppURL)
uissuer, err := url.Parse(deps.RuntimeConfig.AppURL)
if err != nil {
return nil, fmt.Errorf("failed to parse app url: %w", err)
@@ -181,14 +171,14 @@ func NewOIDCService(i OIDCServiceInput) (*OIDCService, error) {
issuer := fmt.Sprintf("%s://%s", uissuer.Scheme, uissuer.Host)
// Create/load private and public keys
if strings.TrimSpace(i.Config.OIDC.PrivateKeyPath) == "" ||
strings.TrimSpace(i.Config.OIDC.PublicKeyPath) == "" {
if strings.TrimSpace(deps.StaticConfig.OIDC.PrivateKeyPath) == "" ||
strings.TrimSpace(deps.StaticConfig.OIDC.PublicKeyPath) == "" {
return nil, errors.New("private key path and public key path are required")
}
var privateKey *rsa.PrivateKey
fprivateKey, err := os.ReadFile(i.Config.OIDC.PrivateKeyPath)
fprivateKey, err := os.ReadFile(deps.StaticConfig.OIDC.PrivateKeyPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err
@@ -207,12 +197,8 @@ func NewOIDCService(i OIDCServiceInput) (*OIDCService, error) {
Type: "RSA PRIVATE KEY",
Bytes: der,
})
i.Log.App.Trace().Str("type", "RSA PRIVATE KEY").Msg("Generated private RSA key")
err := os.MkdirAll(filepath.Dir(i.Config.OIDC.PrivateKeyPath), 0700)
if err != nil {
return nil, fmt.Errorf("failed to create directory for private key: %w", err)
}
err = os.WriteFile(i.Config.OIDC.PrivateKeyPath, encoded, 0600)
deps.Log.App.Trace().Str("type", "RSA PRIVATE KEY").Msg("Generated private RSA key")
err = os.WriteFile(deps.StaticConfig.OIDC.PrivateKeyPath, encoded, 0600)
if err != nil {
return nil, fmt.Errorf("failed to write private key to file: %w", err)
}
@@ -221,7 +207,7 @@ func NewOIDCService(i OIDCServiceInput) (*OIDCService, error) {
if block == nil {
return nil, errors.New("failed to decode private key")
}
i.Log.App.Trace().Str("type", block.Type).Msg("Loaded private key")
deps.Log.App.Trace().Str("type", block.Type).Msg("Loaded private key")
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %w", err)
@@ -230,7 +216,7 @@ func NewOIDCService(i OIDCServiceInput) (*OIDCService, error) {
var publicKey crypto.PublicKey
fpublicKey, err := os.ReadFile(i.Config.OIDC.PublicKeyPath)
fpublicKey, err := os.ReadFile(deps.StaticConfig.OIDC.PublicKeyPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("failed to read public key: %w", err)
@@ -246,12 +232,8 @@ func NewOIDCService(i OIDCServiceInput) (*OIDCService, error) {
Type: "RSA PUBLIC KEY",
Bytes: der,
})
i.Log.App.Trace().Str("type", "RSA PUBLIC KEY").Msg("Generated public RSA key")
err := os.MkdirAll(filepath.Dir(i.Config.OIDC.PublicKeyPath), 0700)
if err != nil {
return nil, fmt.Errorf("failed to create directory for public key: %w", err)
}
err = os.WriteFile(i.Config.OIDC.PublicKeyPath, encoded, 0644)
deps.Log.App.Trace().Str("type", "RSA PUBLIC KEY").Msg("Generated public RSA key")
err = os.WriteFile(deps.StaticConfig.OIDC.PublicKeyPath, encoded, 0644)
if err != nil {
return nil, err
}
@@ -260,7 +242,7 @@ func NewOIDCService(i OIDCServiceInput) (*OIDCService, error) {
if block == nil {
return nil, errors.New("failed to decode public key")
}
i.Log.App.Trace().Str("type", block.Type).Msg("Loaded public key")
deps.Log.App.Trace().Str("type", block.Type).Msg("Loaded public key")
switch block.Type {
case "RSA PUBLIC KEY":
publicKey, err = x509.ParsePKCS1PublicKey(block.Bytes)
@@ -290,7 +272,7 @@ func NewOIDCService(i OIDCServiceInput) (*OIDCService, error) {
// We will reorganize the client into a map with the client ID as the key
clients := make(map[string]model.OIDCClientConfig)
for id, client := range i.Config.OIDC.Clients {
for id, client := range deps.StaticConfig.OIDC.Clients {
client.ID = id
if client.Name == "" {
client.Name = utils.Capitalize(client.ID)
@@ -306,15 +288,15 @@ func NewOIDCService(i OIDCServiceInput) (*OIDCService, error) {
}
client.ClientSecretFile = ""
clients[id] = client
i.Log.App.Debug().Str("clientId", client.ClientID).Msg("Loaded OIDC client configuration")
deps.Log.App.Debug().Str("clientId", client.ClientID).Msg("Loaded OIDC client configuration")
}
// Initialize the service
service := &OIDCService{
log: i.Log,
config: i.Config,
runtime: i.Runtime,
queries: i.Queries,
log: deps.Log,
config: deps.StaticConfig,
runtime: deps.RuntimeConfig,
queries: *deps.Queries,
clients: clients,
privateKey: privateKey,
@@ -323,7 +305,7 @@ func NewOIDCService(i OIDCServiceInput) (*OIDCService, error) {
}
// Start cleanup routine
i.Ding.Go(service.cleanupRoutine, ding.RingMinor)
deps.Ding.Go(service.cleanupRoutine, ding.RingMinor)
// Create caches
codeCash := NewCacheStore[AuthorizeCodeEntry](256)
@@ -335,7 +317,7 @@ func NewOIDCService(i OIDCServiceInput) (*OIDCService, error) {
service.caches.authorize = authorize
// Start cache cleanup routine
i.Ding.Go(func(ctx context.Context) {
deps.Ding.Go(func(ctx context.Context) {
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
+1 -10
View File
@@ -9,7 +9,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/repository/memory"
"github.com/tinyauthapp/tinyauth/internal/service"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
)
@@ -68,15 +67,7 @@ func TestCompileUserinfo(t *testing.T) {
ctx := context.TODO()
dg := ding.New(ctx)
store := memory.New()
svc, err := service.NewOIDCService(service.OIDCServiceInput{
Log: log,
Config: &cfg,
Runtime: &runtime,
Queries: store,
Ding: dg,
})
svc, err := service.NewOIDCService(log, cfg, runtime, nil, dg)
require.NoError(t, err)
type testCase struct {
+8 -14
View File
@@ -6,7 +6,6 @@ import (
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
)
type Policy string
@@ -41,28 +40,23 @@ type PolicyEngine struct {
policy Policy
}
type PolicyEngineInput struct {
dig.In
Log *logger.Logger
Config *model.Config
}
func NewPolicyEngine(i PolicyEngineInput) (*PolicyEngine, error) {
func NewPolicyEngine(
deps *ServiceDependencies,
) (*PolicyEngine, error) {
engine := PolicyEngine{
log: i.Log,
log: deps.Log,
rules: make(map[RuleName]Rule),
}
switch i.Config.Auth.ACLs.Policy {
switch deps.StaticConfig.Auth.ACLs.Policy {
case string(PolicyAllow):
i.Log.App.Debug().Msg("Using 'allow' ACL policy: access to apps will be allowed by default unless explicitly blocked")
deps.Log.App.Debug().Msg("Using 'allow' ACL policy: access to apps will be allowed by default unless explicitly blocked")
engine.policy = PolicyAllow
case string(PolicyDeny):
i.Log.App.Debug().Msg("Using 'deny' ACL policy: access to apps will be blocked by default unless explicitly allowed")
deps.Log.App.Debug().Msg("Using 'deny' ACL policy: access to apps will be blocked by default unless explicitly allowed")
engine.policy = PolicyDeny
default:
return nil, fmt.Errorf("invalid acl policy: %s", i.Config.Auth.ACLs.Policy)
return nil, fmt.Errorf("invalid acl policy: %s", deps.StaticConfig.Auth.ACLs.Policy)
}
return &engine, nil
+6 -24
View File
@@ -33,35 +33,23 @@ func TestPolicyEngine(t *testing.T) {
// Engine should fail with invalid policy
cfg.Auth.ACLs.Policy = "invalid_policy"
_, err := service.NewPolicyEngine(service.PolicyEngineInput{
Log: log,
Config: &cfg,
})
_, err := service.NewPolicyEngine(cfg, log)
assert.Error(t, err)
// Engine should initialize with 'allow' policy
cfg.Auth.ACLs.Policy = string(service.PolicyAllow)
engine, err := service.NewPolicyEngine(service.PolicyEngineInput{
Log: log,
Config: &cfg,
})
engine, err := service.NewPolicyEngine(cfg, log)
assert.NoError(t, err)
assert.Equal(t, service.PolicyAllow, engine.Policy())
// Engine should initialize with 'deny' policy
cfg.Auth.ACLs.Policy = string(service.PolicyDeny)
engine, err = service.NewPolicyEngine(service.PolicyEngineInput{
Log: log,
Config: &cfg,
})
engine, err = service.NewPolicyEngine(cfg, log)
assert.NoError(t, err)
assert.Equal(t, service.PolicyDeny, engine.Policy())
// Engine should allow adding rules
engine, err = service.NewPolicyEngine(service.PolicyEngineInput{
Log: log,
Config: &cfg,
})
engine, err = service.NewPolicyEngine(cfg, log)
assert.NoError(t, err)
engine.RegisterRule("test-rule", testRule)
_, ok := engine.Rules()["test-rule"]
@@ -69,10 +57,7 @@ func TestPolicyEngine(t *testing.T) {
// Begin allow policy tests
cfg.Auth.ACLs.Policy = string(service.PolicyAllow)
engine, err = service.NewPolicyEngine(service.PolicyEngineInput{
Log: log,
Config: &cfg,
})
engine, err = service.NewPolicyEngine(cfg, log)
assert.NoError(t, err)
engine.RegisterRule("test-rule", testRule)
@@ -90,10 +75,7 @@ func TestPolicyEngine(t *testing.T) {
// Begin deny policy tests
cfg.Auth.ACLs.Policy = string(service.PolicyDeny)
engine, err = service.NewPolicyEngine(service.PolicyEngineInput{
Log: log,
Config: &cfg,
})
engine, err = service.NewPolicyEngine(cfg, log)
assert.NoError(t, err)
engine.RegisterRule("test-rule", testRule)
+33
View File
@@ -0,0 +1,33 @@
package service
import (
"context"
"github.com/steveiliop56/ding"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/repository"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
)
type Services struct {
AccessControlService *AccessControlsService
AuthService *AuthService
DockerService *DockerService
KubernetesService *KubernetesService
LDAPService *LdapService
OAuthBrokerService *OAuthBrokerService
OIDCService *OIDCService
TailscaleService *TailscaleService
PolicyEngine *PolicyEngine
}
type ServiceDependencies struct {
Log *logger.Logger
StaticConfig *model.Config
RuntimeConfig *model.RuntimeConfig
Ctx context.Context
Ding *ding.Ding
Services *Services
LabelProvider LabelProvider
Queries *repository.Store
}
+15 -23
View File
@@ -12,7 +12,6 @@ import (
"github.com/steveiliop56/ding"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"go.uber.org/dig"
"tailscale.com/client/local"
"tailscale.com/tsnet"
)
@@ -35,31 +34,24 @@ type TailscaleService struct {
mu sync.Mutex
}
type TailscaleServiceInput struct {
dig.In
Log *logger.Logger
Config *model.Config
Ctx context.Context
Ding *ding.Ding
}
func NewTailscaleService(i TailscaleServiceInput) (*TailscaleService, error) {
if !i.Config.Tailscale.Enabled {
func NewTailscaleService(
deps *ServiceDependencies,
) (*TailscaleService, error) {
if !deps.StaticConfig.Tailscale.Enabled {
return nil, nil
}
srv := new(tsnet.Server)
// node options
srv.Dir = i.Config.Tailscale.Dir
srv.Hostname = i.Config.Tailscale.Hostname
srv.AuthKey = i.Config.Tailscale.AuthKey
srv.Ephemeral = i.Config.Tailscale.Ephemeral
srv.Dir = deps.StaticConfig.Tailscale.Dir
srv.Hostname = deps.StaticConfig.Tailscale.Hostname
srv.AuthKey = deps.StaticConfig.Tailscale.AuthKey
srv.Ephemeral = deps.StaticConfig.Tailscale.Ephemeral
// redirect logs to zerolog
srv.Logf = i.Log.App.Printf
srv.UserLogf = i.Log.App.Printf
srv.Logf = deps.Log.App.Printf
srv.UserLogf = deps.Log.App.Printf
err := srv.Start()
@@ -75,14 +67,14 @@ func NewTailscaleService(i TailscaleServiceInput) (*TailscaleService, error) {
}
service := &TailscaleService{
log: i.Log,
config: i.Config,
ctx: i.Ctx,
log: deps.Log,
config: deps.StaticConfig,
ctx: deps.Ctx,
srv: srv,
lc: lc,
}
connectCtx, cancel := context.WithTimeout(i.Ctx, 2*time.Minute) // large enough timeout to allow for user to manually authenticate with link if needed
connectCtx, cancel := context.WithTimeout(deps.Ctx, 2*time.Minute) // large enough timeout to allow for user to manually authenticate with link if needed
defer cancel()
err = service.waitForConn(connectCtx)
@@ -92,7 +84,7 @@ func NewTailscaleService(i TailscaleServiceInput) (*TailscaleService, error) {
return nil, fmt.Errorf("failed to connect to tailscale network: %w", err)
}
i.Ding.Go(service.watchAndClose, ding.RingMajor)
deps.Ding.Go(service.watchAndClose, ding.RingMajor)
return service, nil
}
+8
View File
@@ -121,6 +121,14 @@ func CreateTestConfigs(t *testing.T) (model.Config, model.RuntimeConfig) {
CookieDomain: "example.com",
AppURL: "https://tinyauth.example.com",
SessionCookieName: "tinyauth-session",
OIDCClients: func() []model.OIDCClientConfig {
var clients []model.OIDCClientConfig
for id, client := range config.OIDC.Clients {
client.ID = id
clients = append(clients, client)
}
return clients
}(),
}
return config, runtime