go-api/authentik_test.go

260 lines
7.6 KiB
Go
Raw Normal View History

// SPDX-License-Identifier: EUPL-1.2
package api_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
api "forge.lthn.ai/core/go-api"
)
// ── AuthentikUser ──────────────────────────────────────────────────────
func TestAuthentikUser_Good(t *testing.T) {
u := api.AuthentikUser{
Username: "alice",
Email: "alice@example.com",
Name: "Alice Smith",
UID: "abc-123",
Groups: []string{"editors", "admins"},
Entitlements: []string{"premium"},
JWT: "tok.en.here",
}
if u.Username != "alice" {
t.Fatalf("expected Username=%q, got %q", "alice", u.Username)
}
if u.Email != "alice@example.com" {
t.Fatalf("expected Email=%q, got %q", "alice@example.com", u.Email)
}
if u.Name != "Alice Smith" {
t.Fatalf("expected Name=%q, got %q", "Alice Smith", u.Name)
}
if u.UID != "abc-123" {
t.Fatalf("expected UID=%q, got %q", "abc-123", u.UID)
}
if len(u.Groups) != 2 || u.Groups[0] != "editors" {
t.Fatalf("expected Groups=[editors admins], got %v", u.Groups)
}
if len(u.Entitlements) != 1 || u.Entitlements[0] != "premium" {
t.Fatalf("expected Entitlements=[premium], got %v", u.Entitlements)
}
if u.JWT != "tok.en.here" {
t.Fatalf("expected JWT=%q, got %q", "tok.en.here", u.JWT)
}
}
func TestAuthentikUserHasGroup_Good(t *testing.T) {
u := api.AuthentikUser{
Groups: []string{"editors", "admins"},
}
if !u.HasGroup("admins") {
t.Fatal("expected HasGroup(admins) = true")
}
if !u.HasGroup("editors") {
t.Fatal("expected HasGroup(editors) = true")
}
}
func TestAuthentikUserHasGroup_Bad_Empty(t *testing.T) {
u := api.AuthentikUser{}
if u.HasGroup("admins") {
t.Fatal("expected HasGroup(admins) = false for empty user")
}
}
func TestAuthentikConfig_Good(t *testing.T) {
cfg := api.AuthentikConfig{
Issuer: "https://auth.example.com",
ClientID: "my-client",
TrustedProxy: true,
PublicPaths: []string{"/public", "/docs"},
}
if cfg.Issuer != "https://auth.example.com" {
t.Fatalf("expected Issuer=%q, got %q", "https://auth.example.com", cfg.Issuer)
}
if cfg.ClientID != "my-client" {
t.Fatalf("expected ClientID=%q, got %q", "my-client", cfg.ClientID)
}
if !cfg.TrustedProxy {
t.Fatal("expected TrustedProxy=true")
}
if len(cfg.PublicPaths) != 2 {
t.Fatalf("expected 2 public paths, got %d", len(cfg.PublicPaths))
}
}
// ── Forward auth middleware ────────────────────────────────────────────
func TestForwardAuthHeaders_Good(t *testing.T) {
gin.SetMode(gin.TestMode)
cfg := api.AuthentikConfig{TrustedProxy: true}
e, _ := api.New(api.WithAuthentik(cfg))
var gotUser *api.AuthentikUser
e.Register(&authTestGroup{onRequest: func(c *gin.Context) {
gotUser = api.GetUser(c)
c.JSON(http.StatusOK, api.OK("ok"))
}})
h := e.Handler()
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/v1/check", nil)
req.Header.Set("X-authentik-username", "bob")
req.Header.Set("X-authentik-email", "bob@example.com")
req.Header.Set("X-authentik-name", "Bob Jones")
req.Header.Set("X-authentik-uid", "uid-456")
req.Header.Set("X-authentik-jwt", "jwt.tok.en")
req.Header.Set("X-authentik-groups", "staff|admins|ops")
req.Header.Set("X-authentik-entitlements", "read|write")
h.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
if gotUser == nil {
t.Fatal("expected GetUser to return a user, got nil")
}
if gotUser.Username != "bob" {
t.Fatalf("expected Username=%q, got %q", "bob", gotUser.Username)
}
if gotUser.Email != "bob@example.com" {
t.Fatalf("expected Email=%q, got %q", "bob@example.com", gotUser.Email)
}
if gotUser.Name != "Bob Jones" {
t.Fatalf("expected Name=%q, got %q", "Bob Jones", gotUser.Name)
}
if gotUser.UID != "uid-456" {
t.Fatalf("expected UID=%q, got %q", "uid-456", gotUser.UID)
}
if gotUser.JWT != "jwt.tok.en" {
t.Fatalf("expected JWT=%q, got %q", "jwt.tok.en", gotUser.JWT)
}
if len(gotUser.Groups) != 3 {
t.Fatalf("expected 3 groups, got %d: %v", len(gotUser.Groups), gotUser.Groups)
}
if gotUser.Groups[0] != "staff" || gotUser.Groups[1] != "admins" || gotUser.Groups[2] != "ops" {
t.Fatalf("expected groups [staff admins ops], got %v", gotUser.Groups)
}
if len(gotUser.Entitlements) != 2 {
t.Fatalf("expected 2 entitlements, got %d: %v", len(gotUser.Entitlements), gotUser.Entitlements)
}
if gotUser.Entitlements[0] != "read" || gotUser.Entitlements[1] != "write" {
t.Fatalf("expected entitlements [read write], got %v", gotUser.Entitlements)
}
}
func TestForwardAuthHeaders_Good_NoHeaders(t *testing.T) {
gin.SetMode(gin.TestMode)
cfg := api.AuthentikConfig{TrustedProxy: true}
e, _ := api.New(api.WithAuthentik(cfg))
var gotUser *api.AuthentikUser
e.Register(&authTestGroup{onRequest: func(c *gin.Context) {
gotUser = api.GetUser(c)
c.JSON(http.StatusOK, api.OK("ok"))
}})
h := e.Handler()
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/v1/check", nil)
h.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
if gotUser != nil {
t.Fatalf("expected GetUser to return nil without headers, got %+v", gotUser)
}
}
func TestForwardAuthHeaders_Bad_NotTrusted(t *testing.T) {
gin.SetMode(gin.TestMode)
cfg := api.AuthentikConfig{TrustedProxy: false}
e, _ := api.New(api.WithAuthentik(cfg))
var gotUser *api.AuthentikUser
e.Register(&authTestGroup{onRequest: func(c *gin.Context) {
gotUser = api.GetUser(c)
c.JSON(http.StatusOK, api.OK("ok"))
}})
h := e.Handler()
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/v1/check", nil)
req.Header.Set("X-authentik-username", "mallory")
req.Header.Set("X-authentik-email", "mallory@evil.com")
h.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
if gotUser != nil {
t.Fatalf("expected GetUser to return nil when TrustedProxy=false, got %+v", gotUser)
}
}
func TestHealthBypassesAuthentik_Good(t *testing.T) {
gin.SetMode(gin.TestMode)
cfg := api.AuthentikConfig{TrustedProxy: true}
e, _ := api.New(api.WithAuthentik(cfg))
h := e.Handler()
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/health", nil)
h.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200 for /health, got %d", w.Code)
}
}
func TestGetUser_Good_NilContext(t *testing.T) {
gin.SetMode(gin.TestMode)
// Engine without WithAuthentik — GetUser should return nil.
e, _ := api.New()
var gotUser *api.AuthentikUser
e.Register(&authTestGroup{onRequest: func(c *gin.Context) {
gotUser = api.GetUser(c)
c.JSON(http.StatusOK, api.OK("ok"))
}})
h := e.Handler()
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/v1/check", nil)
h.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
if gotUser != nil {
t.Fatalf("expected GetUser to return nil without middleware, got %+v", gotUser)
}
}
// ── Test helpers ───────────────────────────────────────────────────────
// authTestGroup provides a /v1/check endpoint that calls a custom handler.
type authTestGroup struct {
onRequest func(c *gin.Context)
}
func (a *authTestGroup) Name() string { return "auth-test" }
func (a *authTestGroup) BasePath() string { return "/v1" }
func (a *authTestGroup) RegisterRoutes(rg *gin.RouterGroup) {
rg.GET("/check", a.onRequest)
}