Add projects

This commit is contained in:
Alois 2026-08-24 00:10:41 +02:00
commit 8b607dd700
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
1802 changed files with 503346 additions and 2 deletions

View file

@ -0,0 +1,90 @@
package access
import (
"fmt"
"net/http"
"strings"
)
// AuthErrorCode classifies authentication failures.
type AuthErrorCode string
const (
AuthErrorCodeNoCredentials AuthErrorCode = "no_credentials"
AuthErrorCodeInvalidCredential AuthErrorCode = "invalid_credential"
AuthErrorCodeNotHandled AuthErrorCode = "not_handled"
AuthErrorCodeInternal AuthErrorCode = "internal_error"
)
// AuthError carries authentication failure details and HTTP status.
type AuthError struct {
Code AuthErrorCode
Message string
StatusCode int
Cause error
}
func (e *AuthError) Error() string {
if e == nil {
return ""
}
message := strings.TrimSpace(e.Message)
if message == "" {
message = "authentication error"
}
if e.Cause != nil {
return fmt.Sprintf("%s: %v", message, e.Cause)
}
return message
}
func (e *AuthError) Unwrap() error {
if e == nil {
return nil
}
return e.Cause
}
// HTTPStatusCode returns a safe fallback for missing status codes.
func (e *AuthError) HTTPStatusCode() int {
if e == nil || e.StatusCode <= 0 {
return http.StatusInternalServerError
}
return e.StatusCode
}
func newAuthError(code AuthErrorCode, message string, statusCode int, cause error) *AuthError {
return &AuthError{
Code: code,
Message: message,
StatusCode: statusCode,
Cause: cause,
}
}
func NewNoCredentialsError() *AuthError {
return newAuthError(AuthErrorCodeNoCredentials, "Missing API key", http.StatusUnauthorized, nil)
}
func NewInvalidCredentialError() *AuthError {
return newAuthError(AuthErrorCodeInvalidCredential, "Invalid API key", http.StatusUnauthorized, nil)
}
func NewNotHandledError() *AuthError {
return newAuthError(AuthErrorCodeNotHandled, "authentication provider did not handle request", 0, nil)
}
func NewInternalAuthError(message string, cause error) *AuthError {
normalizedMessage := strings.TrimSpace(message)
if normalizedMessage == "" {
normalizedMessage = "Authentication service error"
}
return newAuthError(AuthErrorCodeInternal, normalizedMessage, http.StatusInternalServerError, cause)
}
func IsAuthErrorCode(authErr *AuthError, code AuthErrorCode) bool {
if authErr == nil {
return false
}
return authErr.Code == code
}

View file

@ -0,0 +1,88 @@
package access
import (
"context"
"net/http"
"sync"
)
// Manager coordinates authentication providers.
type Manager struct {
mu sync.RWMutex
providers []Provider
}
// NewManager constructs an empty manager.
func NewManager() *Manager {
return &Manager{}
}
// SetProviders replaces the active provider list.
func (m *Manager) SetProviders(providers []Provider) {
if m == nil {
return
}
cloned := make([]Provider, len(providers))
copy(cloned, providers)
m.mu.Lock()
m.providers = cloned
m.mu.Unlock()
}
// Providers returns a snapshot of the active providers.
func (m *Manager) Providers() []Provider {
if m == nil {
return nil
}
m.mu.RLock()
defer m.mu.RUnlock()
snapshot := make([]Provider, len(m.providers))
copy(snapshot, m.providers)
return snapshot
}
// Authenticate evaluates providers until one succeeds.
func (m *Manager) Authenticate(ctx context.Context, r *http.Request) (*Result, *AuthError) {
if m == nil {
return nil, nil
}
providers := m.Providers()
if len(providers) == 0 {
return nil, nil
}
var (
missing bool
invalid bool
)
for _, provider := range providers {
if provider == nil {
continue
}
res, authErr := provider.Authenticate(ctx, r)
if authErr == nil {
return res, nil
}
if IsAuthErrorCode(authErr, AuthErrorCodeNotHandled) {
continue
}
if IsAuthErrorCode(authErr, AuthErrorCodeNoCredentials) {
missing = true
continue
}
if IsAuthErrorCode(authErr, AuthErrorCodeInvalidCredential) {
invalid = true
continue
}
return nil, authErr
}
if invalid {
return nil, NewInvalidCredentialError()
}
if missing {
return nil, NewNoCredentialsError()
}
return nil, NewNoCredentialsError()
}

View file

@ -0,0 +1,105 @@
package access
import (
"context"
"net/http"
"strings"
"sync"
)
// Provider validates credentials for incoming requests.
type Provider interface {
Identifier() string
Authenticate(ctx context.Context, r *http.Request) (*Result, *AuthError)
}
// Result conveys authentication outcome.
type Result struct {
Provider string
Principal string
Metadata map[string]string
}
var (
registryMu sync.RWMutex
registry = make(map[string]Provider)
order []string
exclusiveProvider string
)
// RegisterProvider registers a pre-built provider instance for a given type identifier.
func RegisterProvider(typ string, provider Provider) {
normalizedType := strings.TrimSpace(typ)
if normalizedType == "" || provider == nil {
return
}
registryMu.Lock()
if _, exists := registry[normalizedType]; !exists {
order = append(order, normalizedType)
}
registry[normalizedType] = provider
registryMu.Unlock()
}
// UnregisterProvider removes a provider by type identifier.
func UnregisterProvider(typ string) {
normalizedType := strings.TrimSpace(typ)
if normalizedType == "" {
return
}
registryMu.Lock()
if _, exists := registry[normalizedType]; !exists {
registryMu.Unlock()
return
}
delete(registry, normalizedType)
for index := range order {
if order[index] != normalizedType {
continue
}
order = append(order[:index], order[index+1:]...)
break
}
registryMu.Unlock()
}
// SetExclusiveProvider restricts RegisteredProviders to a single provider key when present.
func SetExclusiveProvider(typ string) {
normalizedType := strings.TrimSpace(typ)
registryMu.Lock()
exclusiveProvider = normalizedType
registryMu.Unlock()
}
// ClearExclusiveProvider removes any active provider restriction.
func ClearExclusiveProvider() {
registryMu.Lock()
exclusiveProvider = ""
registryMu.Unlock()
}
// RegisteredProviders returns the global provider instances in registration order.
func RegisteredProviders() []Provider {
registryMu.RLock()
if len(order) == 0 {
registryMu.RUnlock()
return nil
}
if exclusiveProvider != "" {
if provider, exists := registry[exclusiveProvider]; exists && provider != nil {
registryMu.RUnlock()
return []Provider{provider}
}
}
providers := make([]Provider, 0, len(order))
for _, providerType := range order {
provider, exists := registry[providerType]
if !exists || provider == nil {
continue
}
providers = append(providers, provider)
}
registryMu.RUnlock()
return providers
}

View file

@ -0,0 +1,81 @@
package access
import (
"context"
"net/http"
"testing"
)
type testProvider struct {
id string
}
func (p testProvider) Identifier() string {
return p.id
}
func (p testProvider) Authenticate(context.Context, *http.Request) (*Result, *AuthError) {
return &Result{Provider: p.id, Principal: p.id}, nil
}
func TestRegisteredProvidersReturnsOnlyExclusiveProvider(t *testing.T) {
UnregisterProvider("test-a")
UnregisterProvider("test-b")
ClearExclusiveProvider()
defer UnregisterProvider("test-a")
defer UnregisterProvider("test-b")
defer ClearExclusiveProvider()
RegisterProvider("test-a", testProvider{id: "test-a"})
RegisterProvider("test-b", testProvider{id: "test-b"})
SetExclusiveProvider("test-b")
providers := RegisteredProviders()
if len(providers) != 1 {
t.Fatalf("RegisteredProviders() len = %d, want 1", len(providers))
}
if providers[0].Identifier() != "test-b" {
t.Fatalf("RegisteredProviders()[0] = %q, want test-b", providers[0].Identifier())
}
}
func TestRegisteredProvidersRestoresAllProvidersAfterExclusiveCleared(t *testing.T) {
UnregisterProvider("test-a")
UnregisterProvider("test-b")
ClearExclusiveProvider()
defer UnregisterProvider("test-a")
defer UnregisterProvider("test-b")
defer ClearExclusiveProvider()
RegisterProvider("test-a", testProvider{id: "test-a"})
RegisterProvider("test-b", testProvider{id: "test-b"})
SetExclusiveProvider("test-b")
ClearExclusiveProvider()
providers := RegisteredProviders()
if len(providers) != 2 {
t.Fatalf("RegisteredProviders() len = %d, want 2", len(providers))
}
if providers[0].Identifier() != "test-a" || providers[1].Identifier() != "test-b" {
t.Fatalf("RegisteredProviders() = [%q, %q], want [test-a, test-b]", providers[0].Identifier(), providers[1].Identifier())
}
}
func TestRegisteredProvidersIgnoresStaleExclusiveProvider(t *testing.T) {
UnregisterProvider("test-a")
UnregisterProvider("missing")
ClearExclusiveProvider()
defer UnregisterProvider("test-a")
defer ClearExclusiveProvider()
RegisterProvider("test-a", testProvider{id: "test-a"})
SetExclusiveProvider("missing")
providers := RegisteredProviders()
if len(providers) != 1 {
t.Fatalf("RegisteredProviders() len = %d, want 1", len(providers))
}
if providers[0].Identifier() != "test-a" {
t.Fatalf("RegisteredProviders()[0] = %q, want test-a", providers[0].Identifier())
}
}

View file

@ -0,0 +1,47 @@
package access
// AccessConfig groups request authentication providers.
type AccessConfig struct {
// Providers lists configured authentication providers.
Providers []AccessProvider `yaml:"providers,omitempty" json:"providers,omitempty"`
}
// AccessProvider describes a request authentication provider entry.
type AccessProvider struct {
// Name is the instance identifier for the provider.
Name string `yaml:"name" json:"name"`
// Type selects the provider implementation registered via the SDK.
Type string `yaml:"type" json:"type"`
// SDK optionally names a third-party SDK module providing this provider.
SDK string `yaml:"sdk,omitempty" json:"sdk,omitempty"`
// APIKeys lists inline keys for providers that require them.
APIKeys []string `yaml:"api-keys,omitempty" json:"api-keys,omitempty"`
// Config passes provider-specific options to the implementation.
Config map[string]any `yaml:"config,omitempty" json:"config,omitempty"`
}
const (
// AccessProviderTypeConfigAPIKey is the built-in provider validating inline API keys.
AccessProviderTypeConfigAPIKey = "config-api-key"
// DefaultAccessProviderName is applied when no provider name is supplied.
DefaultAccessProviderName = "config-inline"
)
// MakeInlineAPIKeyProvider constructs an inline API key provider configuration.
// It returns nil when no keys are supplied.
func MakeInlineAPIKeyProvider(keys []string) *AccessProvider {
if len(keys) == 0 {
return nil
}
provider := &AccessProvider{
Name: DefaultAccessProviderName,
Type: AccessProviderTypeConfigAPIKey,
APIKeys: append([]string(nil), keys...),
}
return provider
}