//go:build auth package http_test import ( "context" "net/http" "net/http/httptest" "os" "path/filepath" "strconv" "strings" "github.com/labstack/echo/v4" "github.com/mudler/LocalAI/core/application" "github.com/mudler/LocalAI/core/config" . "github.com/mudler/LocalAI/core/http" "github.com/mudler/LocalAI/pkg/system" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) // Every route registered by API() must either reject anonymous traffic with // 401 or appear on the explicit public allowlist below. The test fails on // routes that ship without an auth decision; adding a new public surface // should be deliberate, not a side effect. var _ = Describe("Route auth coverage", func() { var ( app *echo.Echo tmpdir string c context.Context cancel context.CancelFunc appInst *application.Application ) BeforeEach(func() { var err error tmpdir, err = os.MkdirTemp("", "route-coverage-") Expect(err).ToNot(HaveOccurred()) modelDir := filepath.Join(tmpdir, "models") Expect(os.Mkdir(modelDir, 0750)).To(Succeed()) bDir := filepath.Join(tmpdir, "backends") Expect(os.Mkdir(bDir, 0750)).To(Succeed()) c, cancel = context.WithCancel(context.Background()) systemState, err := system.GetSystemState( system.WithBackendPath(bDir), system.WithModelPath(modelDir), ) Expect(err).ToNot(HaveOccurred()) // Auth enabled, no legacy keys, no admin user pre-created. With auth // enabled the global middleware MUST reject anonymous API requests // regardless of admin presence. appInst, err = application.New( config.WithContext(c), config.WithSystemState(systemState), config.WithAuthEnabled(true), config.WithAuthDatabaseURL(":memory:"), config.WithAuthAPIKeyHMACSecret("test-secret-for-route-coverage"), ) Expect(err).ToNot(HaveOccurred()) app, err = API(appInst) Expect(err).ToNot(HaveOccurred()) }) AfterEach(func() { cancel() Expect(os.RemoveAll(tmpdir)).To(Succeed()) }) It("exposes only session controls and duplex streaming for motion", func() { var motionRoutes []string for _, route := range app.Routes() { if strings.HasPrefix(route.Path, "/api/motion/") { motionRoutes = append(motionRoutes, route.Method+" "+route.Path) } } Expect(motionRoutes).To(ConsistOf( "POST /api/motion/sessions", "GET /api/motion/sessions/:id", "DELETE /api/motion/sessions/:id", "POST /api/motion/sessions/:id/tickets", "GET /api/motion/sessions/:id/poses", )) }) It("enforces the anonymous-access decision for every registered route", func() { type routePattern struct { method string path string } // This allowlist deliberately restates the public contract instead of // importing the production registry, so drift in either direction fails. expectedPublicRoutes := map[routePattern]struct{}{ // Discovery used before clients have credentials. {method: http.MethodGet, path: "/.well-known/localai.json"}: {}, {method: http.MethodGet, path: "/api/instructions"}: {}, {method: http.MethodGet, path: "/api/instructions/:name"}: {}, {method: http.MethodGet, path: "/swagger"}: {}, {method: http.MethodGet, path: "/swagger/"}: {}, {method: http.MethodGet, path: "/swagger/index.html"}: {}, {method: http.MethodGet, path: "/swagger/*"}: {}, // Orchestrator health probes. {method: http.MethodGet, path: "/healthz"}: {}, {method: http.MethodGet, path: "/readyz"}: {}, // Authentication bootstrap endpoints only; authenticated account and // admin operations under /api/auth/ remain protected. {method: http.MethodGet, path: "/api/auth/status"}: {}, {method: http.MethodPost, path: "/api/auth/token-login"}: {}, {method: http.MethodPost, path: "/api/auth/register"}: {}, {method: http.MethodPost, path: "/api/auth/login"}: {}, {method: http.MethodGet, path: "/api/auth/github/login"}: {}, {method: http.MethodGet, path: "/api/auth/github/callback"}: {}, {method: http.MethodGet, path: "/api/auth/oidc/login"}: {}, {method: http.MethodGet, path: "/api/auth/oidc/callback"}: {}, // SPA shell and client-side navigation before login. {method: http.MethodGet, path: "/"}: {}, {method: http.MethodHead, path: "/"}: {}, {method: http.MethodGet, path: "/app"}: {}, {method: http.MethodGet, path: "/app/*"}: {}, {method: http.MethodGet, path: "/browse"}: {}, {method: http.MethodGet, path: "/browse/*"}: {}, {method: http.MethodGet, path: "/login"}: {}, {method: http.MethodGet, path: "/invite/:code"}: {}, {method: http.MethodGet, path: "/explorer"}: {}, // Static assets needed to render the pre-authentication UI. {method: http.MethodGet, path: "/favicon.svg"}: {}, {method: http.MethodGet, path: "/assets/*"}: {}, {method: http.MethodGet, path: "/locales/*"}: {}, {method: http.MethodGet, path: "/static/*"}: {}, // Canvas preview frames: the random id is the capability // (an