diff --git a/authentik.go b/authentik.go index 9e8b4eb..f42aa38 100644 --- a/authentik.go +++ b/authentik.go @@ -26,7 +26,7 @@ type AuthentikConfig struct { TrustedProxy bool // PublicPaths lists additional paths that do not require authentication. - // /health and /swagger are always public. + // /health and the configured Swagger UI path are always public. PublicPaths []string } @@ -134,7 +134,7 @@ func validateJWT(ctx context.Context, cfg AuthentikConfig, rawToken string) (*Au // The middleware is PERMISSIVE: it populates the context when credentials are // present but never rejects unauthenticated requests. Downstream handlers // use GetUser to check authentication. -func authentikMiddleware(cfg AuthentikConfig) gin.HandlerFunc { +func authentikMiddleware(cfg AuthentikConfig, publicPaths func() []string) gin.HandlerFunc { // Build the set of public paths that skip header extraction entirely. public := map[string]bool{ "/health": true, @@ -153,6 +153,14 @@ func authentikMiddleware(cfg AuthentikConfig) gin.HandlerFunc { return } } + if publicPaths != nil { + for _, p := range publicPaths() { + if isPublicPath(path, p) { + c.Next() + return + } + } + } // Block 1: Extract user from X-authentik-* forward-auth headers. if cfg.TrustedProxy { diff --git a/authentik_test.go b/authentik_test.go index 0422100..b44b7c8 100644 --- a/authentik_test.go +++ b/authentik_test.go @@ -343,6 +343,33 @@ func TestBearerAndAuthentikCoexist_Good(t *testing.T) { } } +func TestAuthentik_Good_CustomSwaggerPathBypassesAuth(t *testing.T) { + gin.SetMode(gin.TestMode) + + cfg := api.AuthentikConfig{TrustedProxy: true} + e, err := api.New( + api.WithAuthentik(cfg), + api.WithSwagger("Test API", "A test API service", "1.0.0"), + api.WithSwaggerPath("/docs"), + ) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + srv := httptest.NewServer(e.Handler()) + defer srv.Close() + + resp, err := http.Get(srv.URL + "/docs/doc.json") + if err != nil { + t.Fatalf("request failed: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Fatalf("expected 200 for custom swagger path without auth, got %d", resp.StatusCode) + } +} + // ── RequireAuth / RequireGroup ──────────────────────────────────────── func TestRequireAuth_Good(t *testing.T) { diff --git a/options.go b/options.go index a608c75..bc2072a 100644 --- a/options.go +++ b/options.go @@ -140,7 +140,9 @@ func WithWSPath(path string) Option { // The middleware is permissive: unauthenticated requests are allowed through. func WithAuthentik(cfg AuthentikConfig) Option { return func(e *Engine) { - e.middlewares = append(e.middlewares, authentikMiddleware(cfg)) + e.middlewares = append(e.middlewares, authentikMiddleware(cfg, func() []string { + return []string{resolveSwaggerPath(e.swaggerPath)} + })) } }