~bigbes/lethe

717e25b528bcecf9364e792de85b8b467a137ef4 — Eugene Blikh a month ago 859d3fd
config: add auth.oidc.dev_stub block (disabled by default)
3 files changed, 70 insertions(+), 4 deletions(-)

M config.example.yaml
M internal/config/config.go
M internal/config/config_test.go
M config.example.yaml => config.example.yaml +5 -0
@@ 17,6 17,11 @@ auth:
    issuer: "https://auth.example.com"
    audience: "lethe"
    username_claim: "preferred_username"
    # dev_stub: local-dev only — never enable in production
    # dev_stub:
    #   enabled: false
    #   bind: "127.0.0.1:8181"
    #   token_ttl: "24h"

logging:
  level: "info"

M internal/config/config.go => internal/config/config.go +15 -4
@@ 67,10 67,20 @@ type ForwardAuthConfig struct {

// OIDCConfig validates bearer tokens against an OIDC issuer.
type OIDCConfig struct {
	Enabled       bool   `mapstructure:"enabled"`
	Issuer        string `mapstructure:"issuer"         validate:"required_if=Enabled true,omitempty,url"`
	Audience      string `mapstructure:"audience"       validate:"required_if=Enabled true"`
	UsernameClaim string `mapstructure:"username_claim"`
	Enabled       bool              `mapstructure:"enabled"`
	Issuer        string            `mapstructure:"issuer"         validate:"required_if=Enabled true,omitempty,url"`
	Audience      string            `mapstructure:"audience"       validate:"required_if=Enabled true"`
	UsernameClaim string            `mapstructure:"username_claim"`
	DevStub       OIDCDevStubConfig `mapstructure:"dev_stub"`
}

// OIDCDevStubConfig controls the local-dev OIDC stub server. It must never
// be enabled in production (see AS1). When disabled (the default), nothing
// starts and no listener opens.
type OIDCDevStubConfig struct {
	Enabled  bool          `mapstructure:"enabled"`
	Bind     string        `mapstructure:"bind"      validate:"required_if=Enabled true,omitempty,loopback_bind"`
	TokenTTL time.Duration `mapstructure:"token_ttl" validate:"omitempty,gt=0"`
}

// LoggingConfig selects log level and formatter.


@@ 159,6 169,7 @@ func registerDefaults(v *viper.Viper) {
	v.SetDefault("database.busy_timeout", 5*time.Second)
	v.SetDefault("auth.forward_auth.user_header", "Remote-User")
	v.SetDefault("auth.oidc.username_claim", "preferred_username")
	v.SetDefault("auth.oidc.dev_stub.token_ttl", 24*time.Hour)
	v.SetDefault("ingest.max_body_bytes", int64(16*1024*1024))
	v.SetDefault("ingest.max_turn_content_bytes", int64(4*1024*1024))
	v.SetDefault("ingest.chunk_size", 500)

M internal/config/config_test.go => internal/config/config_test.go +50 -0
@@ 49,6 49,29 @@ logging:
ingest: {}
`

const validOIDCDevStubYAML = `
server:
  bind: "127.0.0.1:8080"
database:
  path: "./lethe.db"
auth:
  allowed_users: ["alice"]
  forward_auth:
    enabled: false
  oidc:
    enabled: true
    issuer: "https://auth.example.com"
    audience: "lethe"
    dev_stub:
      enabled: true
      bind: "127.0.0.1:8181"
      token_ttl: "1h"
logging:
  level: "info"
  format: "tint"
ingest: {}
`

const validBothEnabledYAML = `
server:
  bind: "127.0.0.1:8080"


@@ 113,6 136,30 @@ func TestLoad_ValidBothEnabled(t *testing.T) {
	}
}

func TestLoad_ValidOIDCDevStub(t *testing.T) {
	path := writeYAML(t, validOIDCDevStubYAML)
	cfg, err := config.Load(path)
	if err != nil {
		t.Fatalf("Load: %v", err)
	}
	if !cfg.Auth.OIDC.DevStub.Enabled {
		t.Error("DevStub.Enabled = false, want true")
	}
	if cfg.Auth.OIDC.DevStub.Bind != "127.0.0.1:8181" {
		t.Errorf("DevStub.Bind = %q, want 127.0.0.1:8181", cfg.Auth.OIDC.DevStub.Bind)
	}
	if cfg.Auth.OIDC.DevStub.TokenTTL != time.Hour {
		t.Errorf("DevStub.TokenTTL = %s, want 1h", cfg.Auth.OIDC.DevStub.TokenTTL)
	}
}

func TestLoad_DevStubBindNonLoopback_Rejects(t *testing.T) {
	body := strings.Replace(validOIDCDevStubYAML, `"127.0.0.1:8181"`, `"0.0.0.0:8181"`, 1)
	if _, err := config.Load(writeYAML(t, body)); err == nil {
		t.Fatal("expected error for non-loopback dev_stub bind, got nil")
	}
}

func TestLoad_Defaults(t *testing.T) {
	path := writeYAML(t, validForwardAuthYAML)
	cfg, err := config.Load(path)


@@ 140,6 187,9 @@ func TestLoad_Defaults(t *testing.T) {
	if cfg.Server.ShutdownGrace != 10*time.Second {
		t.Errorf("Server.ShutdownGrace = %s, want 10s", cfg.Server.ShutdownGrace)
	}
	if cfg.Auth.OIDC.DevStub.TokenTTL != 24*time.Hour {
		t.Errorf("OIDC.DevStub.TokenTTL = %s, want 24h", cfg.Auth.OIDC.DevStub.TokenTTL)
	}
}

func TestLoad_EmptyAllowlistRejected(t *testing.T) {