feat(api): snapshot authentik runtime config

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-02 13:17:08 +00:00
parent 0171f9ad49
commit eb18611dc1
5 changed files with 89 additions and 6 deletions

1
api.go
View file

@ -59,6 +59,7 @@ type Engine struct {
swaggerSecuritySchemes map[string]any
swaggerExternalDocsDescription string
swaggerExternalDocsURL string
authentikConfig AuthentikConfig
pprofEnabled bool
expvarEnabled bool
ssePath string

View file

@ -34,6 +34,22 @@ type AuthentikConfig struct {
PublicPaths []string
}
// AuthentikConfig returns the configured Authentik settings for the engine.
//
// The result snapshots the Engine state at call time and clones slices so
// callers can safely reuse or modify the returned value.
//
// Example:
//
// cfg := engine.AuthentikConfig()
func (e *Engine) AuthentikConfig() AuthentikConfig {
if e == nil {
return AuthentikConfig{}
}
return cloneAuthentikConfig(e.authentikConfig)
}
// AuthentikUser represents an authenticated user extracted from Authentik
// forward-auth headers or a validated JWT.
//
@ -217,6 +233,12 @@ func authentikMiddleware(cfg AuthentikConfig, publicPaths func() []string) gin.H
}
}
func cloneAuthentikConfig(cfg AuthentikConfig) AuthentikConfig {
out := cfg
out.PublicPaths = slices.Clone(cfg.PublicPaths)
return out
}
// RequireAuth is Gin middleware that rejects unauthenticated requests.
// It checks for a user set by the Authentik middleware and returns 401
// when none is present.

View file

@ -127,6 +127,12 @@ func TestEngine_RuntimeConfig_Good_SnapshotsCurrentSettings(t *testing.T) {
api.WithWSPath("/socket"),
api.WithSSE(broker),
api.WithSSEPath("/events"),
api.WithAuthentik(api.AuthentikConfig{
Issuer: "https://auth.example.com",
ClientID: "runtime-client",
TrustedProxy: true,
PublicPaths: []string{"/public", "/docs"},
}),
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
@ -152,17 +158,67 @@ func TestEngine_RuntimeConfig_Good_SnapshotsCurrentSettings(t *testing.T) {
if !slices.Equal(cfg.I18n.Supported, []string{"en-GB", "fr"}) {
t.Fatalf("expected supported locales [en-GB fr], got %v", cfg.I18n.Supported)
}
if cfg.Authentik.Issuer != "https://auth.example.com" {
t.Fatalf("expected Authentik issuer https://auth.example.com, got %q", cfg.Authentik.Issuer)
}
if cfg.Authentik.ClientID != "runtime-client" {
t.Fatalf("expected Authentik client ID runtime-client, got %q", cfg.Authentik.ClientID)
}
if !cfg.Authentik.TrustedProxy {
t.Fatal("expected Authentik trusted proxy to be enabled")
}
if !slices.Equal(cfg.Authentik.PublicPaths, []string{"/public", "/docs"}) {
t.Fatalf("expected Authentik public paths [/public /docs], got %v", cfg.Authentik.PublicPaths)
}
}
func TestEngine_RuntimeConfig_Good_EmptyOnNilEngine(t *testing.T) {
var e *api.Engine
cfg := e.RuntimeConfig()
if cfg.Swagger.Enabled || cfg.Transport.SwaggerEnabled || cfg.Cache.Enabled || cfg.I18n.DefaultLocale != "" {
if cfg.Swagger.Enabled || cfg.Transport.SwaggerEnabled || cfg.Cache.Enabled || cfg.I18n.DefaultLocale != "" || cfg.Authentik.Issuer != "" {
t.Fatalf("expected zero-value runtime config, got %+v", cfg)
}
}
func TestEngine_AuthentikConfig_Good_SnapshotsCurrentSettings(t *testing.T) {
e, _ := api.New(api.WithAuthentik(api.AuthentikConfig{
Issuer: "https://auth.example.com",
ClientID: "client",
TrustedProxy: true,
PublicPaths: []string{"/public", "/docs"},
}))
cfg := e.AuthentikConfig()
if cfg.Issuer != "https://auth.example.com" {
t.Fatalf("expected issuer https://auth.example.com, got %q", cfg.Issuer)
}
if cfg.ClientID != "client" {
t.Fatalf("expected client ID client, got %q", cfg.ClientID)
}
if !cfg.TrustedProxy {
t.Fatal("expected trusted proxy to be enabled")
}
if !slices.Equal(cfg.PublicPaths, []string{"/public", "/docs"}) {
t.Fatalf("expected public paths [/public /docs], got %v", cfg.PublicPaths)
}
}
func TestEngine_AuthentikConfig_Good_ClonesPublicPaths(t *testing.T) {
publicPaths := []string{"/public", "/docs"}
e, _ := api.New(api.WithAuthentik(api.AuthentikConfig{
Issuer: "https://auth.example.com",
PublicPaths: publicPaths,
}))
cfg := e.AuthentikConfig()
publicPaths[0] = "/mutated"
if cfg.PublicPaths[0] != "/public" {
t.Fatalf("expected snapshot to preserve original public paths, got %v", cfg.PublicPaths)
}
}
func TestEngine_Register_Good_IgnoresNilGroups(t *testing.T) {
e, _ := api.New()

View file

@ -173,7 +173,9 @@ func WithWSPath(path string) Option {
// api.New(api.WithAuthentik(api.AuthentikConfig{TrustedProxy: true}))
func WithAuthentik(cfg AuthentikConfig) Option {
return func(e *Engine) {
e.middlewares = append(e.middlewares, authentikMiddleware(cfg, func() []string {
snapshot := cloneAuthentikConfig(cfg)
e.authentikConfig = snapshot
e.middlewares = append(e.middlewares, authentikMiddleware(snapshot, func() []string {
return []string{resolveSwaggerPath(e.swaggerPath)}
}))
}

View file

@ -13,10 +13,11 @@ package api
//
// cfg := engine.RuntimeConfig()
type RuntimeConfig struct {
Swagger SwaggerConfig
Transport TransportConfig
Cache CacheConfig
I18n I18nConfig
Swagger SwaggerConfig
Transport TransportConfig
Cache CacheConfig
I18n I18nConfig
Authentik AuthentikConfig
}
// RuntimeConfig returns a stable snapshot of the engine's current runtime
@ -38,5 +39,6 @@ func (e *Engine) RuntimeConfig() RuntimeConfig {
Transport: e.TransportConfig(),
Cache: e.CacheConfig(),
I18n: e.I18nConfig(),
Authentik: e.AuthentikConfig(),
}
}