Files
LocalAI/core/http/auth/helpers_test.go
Ettore Di Giacinto aea21951a2 feat: add users and authentication support (#9061)
* feat(ui): add users and authentication support

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat: allow the admin user to impersonificate users

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* chore: ui improvements, disable 'Users' button in navbar when no auth is configured

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* feat: add OIDC support

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix: gate models

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* chore: cache requests to optimize speed

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* small UI enhancements

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* chore(ui): style improvements

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix: cover other paths by auth

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* chore: separate local auth, refactor

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* security hardening, approval mode

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* fix: fix tests and expectations

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

* chore: update localagi/localrecall

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-03-19 21:40:51 +01:00

156 lines
4.0 KiB
Go

//go:build auth
package auth_test
import (
"net/http"
"net/http/httptest"
"github.com/labstack/echo/v4"
"github.com/mudler/LocalAI/core/config"
"github.com/mudler/LocalAI/core/http/auth"
. "github.com/onsi/gomega"
"gorm.io/gorm"
)
// testDB creates an in-memory SQLite GORM instance with auto-migration.
func testDB() *gorm.DB {
db, err := auth.InitDB(":memory:")
Expect(err).ToNot(HaveOccurred())
return db
}
// createTestUser inserts a user directly into the DB for test setup.
func createTestUser(db *gorm.DB, email, role, provider string) *auth.User {
user := &auth.User{
ID: generateTestID(),
Email: email,
Name: "Test User",
Provider: provider,
Subject: generateTestID(),
Role: role,
Status: auth.StatusActive,
}
err := db.Create(user).Error
Expect(err).ToNot(HaveOccurred())
return user
}
// createTestSession creates a session for a user, returns plaintext session token.
func createTestSession(db *gorm.DB, userID string) string {
sessionID, err := auth.CreateSession(db, userID, "")
Expect(err).ToNot(HaveOccurred())
return sessionID
}
var testIDCounter int
func generateTestID() string {
testIDCounter++
return "test-id-" + string(rune('a'+testIDCounter))
}
// ok is a simple handler that returns 200 OK.
func ok(c echo.Context) error {
return c.String(http.StatusOK, "ok")
}
// newAuthTestApp creates a minimal Echo app with the new auth middleware.
func newAuthTestApp(db *gorm.DB, appConfig *config.ApplicationConfig) *echo.Echo {
e := echo.New()
e.Use(auth.Middleware(db, appConfig))
// API routes (require auth)
e.GET("/v1/models", ok)
e.POST("/v1/chat/completions", ok)
e.GET("/api/settings", ok)
e.POST("/api/settings", ok)
// Auth routes (exempt)
e.GET("/api/auth/status", ok)
e.GET("/api/auth/github/login", ok)
// Static routes
e.GET("/app", ok)
e.GET("/app/*", ok)
return e
}
// newAdminTestApp creates an Echo app with admin-protected routes.
func newAdminTestApp(db *gorm.DB, appConfig *config.ApplicationConfig) *echo.Echo {
e := echo.New()
e.Use(auth.Middleware(db, appConfig))
// Regular routes
e.GET("/v1/models", ok)
e.POST("/v1/chat/completions", ok)
// Admin-only routes
adminMw := auth.RequireAdmin()
e.POST("/api/settings", ok, adminMw)
e.POST("/models/apply", ok, adminMw)
e.POST("/backends/apply", ok, adminMw)
e.GET("/api/agents", ok, adminMw)
// Trace/log endpoints (admin only)
e.GET("/api/traces", ok, adminMw)
e.POST("/api/traces/clear", ok, adminMw)
e.GET("/api/backend-logs", ok, adminMw)
e.GET("/api/backend-logs/:modelId", ok, adminMw)
// Gallery/management reads (admin only)
e.GET("/api/operations", ok, adminMw)
e.GET("/api/models", ok, adminMw)
e.GET("/api/backends", ok, adminMw)
e.GET("/api/resources", ok, adminMw)
e.GET("/api/p2p/workers", ok, adminMw)
// Agent task/job routes (admin only)
e.POST("/api/agent/tasks", ok, adminMw)
e.GET("/api/agent/tasks", ok, adminMw)
e.GET("/api/agent/jobs", ok, adminMw)
// System info (admin only)
e.GET("/system", ok, adminMw)
e.GET("/backend/monitor", ok, adminMw)
return e
}
// doRequest performs an HTTP request against the given Echo app and returns the recorder.
func doRequest(e *echo.Echo, method, path string, opts ...func(*http.Request)) *httptest.ResponseRecorder {
req := httptest.NewRequest(method, path, nil)
req.Header.Set("Content-Type", "application/json")
for _, opt := range opts {
opt(req)
}
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
return rec
}
func withBearerToken(token string) func(*http.Request) {
return func(req *http.Request) {
req.Header.Set("Authorization", "Bearer "+token)
}
}
func withXApiKey(key string) func(*http.Request) {
return func(req *http.Request) {
req.Header.Set("x-api-key", key)
}
}
func withSessionCookie(sessionID string) func(*http.Request) {
return func(req *http.Request) {
req.AddCookie(&http.Cookie{Name: "session", Value: sessionID})
}
}
func withTokenCookie(token string) func(*http.Request) {
return func(req *http.Request) {
req.AddCookie(&http.Cookie{Name: "token", Value: token})
}
}