feat(authentik): add AuthentikUser and AuthentikConfig types

Introduce core types for the Authentik forward-auth integration:
- AuthentikConfig with Issuer, ClientID, TrustedProxy, PublicPaths
- AuthentikUser with Username, Email, Name, UID, Groups, Entitlements, JWT
- HasGroup helper for group membership checks

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-02-20 16:36:38 +00:00
parent 17ae94530d
commit 6cd3b7e7e0
2 changed files with 130 additions and 0 deletions

42
authentik.go Normal file
View file

@ -0,0 +1,42 @@
// SPDX-License-Identifier: EUPL-1.2
package api
// AuthentikConfig holds settings for the Authentik forward-auth integration.
type AuthentikConfig struct {
// Issuer is the OIDC issuer URL (e.g. https://auth.example.com/application/o/my-app/).
Issuer string
// ClientID is the OAuth2 client identifier.
ClientID string
// TrustedProxy enables reading X-authentik-* headers set by a reverse proxy.
// When false, headers are ignored to prevent spoofing from untrusted sources.
TrustedProxy bool
// PublicPaths lists additional paths that do not require authentication.
// /health and /swagger are always public.
PublicPaths []string
}
// AuthentikUser represents an authenticated user extracted from Authentik
// forward-auth headers or a validated JWT.
type AuthentikUser struct {
Username string `json:"username"`
Email string `json:"email"`
Name string `json:"name"`
UID string `json:"uid"`
Groups []string `json:"groups,omitempty"`
Entitlements []string `json:"entitlements,omitempty"`
JWT string `json:"-"`
}
// HasGroup reports whether the user belongs to the named group.
func (u *AuthentikUser) HasGroup(group string) bool {
for _, g := range u.Groups {
if g == group {
return true
}
}
return false
}

88
authentik_test.go Normal file
View file

@ -0,0 +1,88 @@
// SPDX-License-Identifier: EUPL-1.2
package api_test
import (
"testing"
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))
}
}