mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
rebuild branch
This commit is contained in:
12
sdk/access/errors.go
Normal file
12
sdk/access/errors.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package access
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrNoCredentials indicates no recognizable credentials were supplied.
|
||||
ErrNoCredentials = errors.New("access: no credentials provided")
|
||||
// ErrInvalidCredential signals that supplied credentials were rejected by a provider.
|
||||
ErrInvalidCredential = errors.New("access: invalid credential")
|
||||
// ErrNotHandled tells the manager to continue trying other providers.
|
||||
ErrNotHandled = errors.New("access: not handled")
|
||||
)
|
||||
89
sdk/access/manager.go
Normal file
89
sdk/access/manager.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"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, error) {
|
||||
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, err := provider.Authenticate(ctx, r)
|
||||
if err == nil {
|
||||
return res, nil
|
||||
}
|
||||
if errors.Is(err, ErrNotHandled) {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, ErrNoCredentials) {
|
||||
missing = true
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, ErrInvalidCredential) {
|
||||
invalid = true
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if invalid {
|
||||
return nil, ErrInvalidCredential
|
||||
}
|
||||
if missing {
|
||||
return nil, ErrNoCredentials
|
||||
}
|
||||
return nil, ErrNoCredentials
|
||||
}
|
||||
103
sdk/access/providers/configapikey/provider.go
Normal file
103
sdk/access/providers/configapikey/provider.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package configapikey
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access"
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
name string
|
||||
keys map[string]struct{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
sdkaccess.RegisterProvider(config.AccessProviderTypeConfigAPIKey, newProvider)
|
||||
}
|
||||
|
||||
func newProvider(cfg *config.AccessProvider, _ *config.Config) (sdkaccess.Provider, error) {
|
||||
name := cfg.Name
|
||||
if name == "" {
|
||||
name = config.DefaultAccessProviderName
|
||||
}
|
||||
keys := make(map[string]struct{}, len(cfg.APIKeys))
|
||||
for _, key := range cfg.APIKeys {
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
keys[key] = struct{}{}
|
||||
}
|
||||
return &provider{name: name, keys: keys}, nil
|
||||
}
|
||||
|
||||
func (p *provider) Identifier() string {
|
||||
if p == nil || p.name == "" {
|
||||
return config.DefaultAccessProviderName
|
||||
}
|
||||
return p.name
|
||||
}
|
||||
|
||||
func (p *provider) Authenticate(_ context.Context, r *http.Request) (*sdkaccess.Result, error) {
|
||||
if p == nil {
|
||||
return nil, sdkaccess.ErrNotHandled
|
||||
}
|
||||
if len(p.keys) == 0 {
|
||||
return nil, sdkaccess.ErrNotHandled
|
||||
}
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
authHeaderGoogle := r.Header.Get("X-Goog-Api-Key")
|
||||
authHeaderAnthropic := r.Header.Get("X-Api-Key")
|
||||
queryKey := ""
|
||||
if r.URL != nil {
|
||||
queryKey = r.URL.Query().Get("key")
|
||||
}
|
||||
if authHeader == "" && authHeaderGoogle == "" && authHeaderAnthropic == "" && queryKey == "" {
|
||||
return nil, sdkaccess.ErrNoCredentials
|
||||
}
|
||||
|
||||
apiKey := extractBearerToken(authHeader)
|
||||
|
||||
candidates := []struct {
|
||||
value string
|
||||
source string
|
||||
}{
|
||||
{apiKey, "authorization"},
|
||||
{authHeaderGoogle, "x-goog-api-key"},
|
||||
{authHeaderAnthropic, "x-api-key"},
|
||||
{queryKey, "query-key"},
|
||||
}
|
||||
|
||||
for _, candidate := range candidates {
|
||||
if candidate.value == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := p.keys[candidate.value]; ok {
|
||||
return &sdkaccess.Result{
|
||||
Provider: p.Identifier(),
|
||||
Principal: candidate.value,
|
||||
Metadata: map[string]string{
|
||||
"source": candidate.source,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, sdkaccess.ErrInvalidCredential
|
||||
}
|
||||
|
||||
func extractBearerToken(header string) string {
|
||||
if header == "" {
|
||||
return ""
|
||||
}
|
||||
parts := strings.SplitN(header, " ", 2)
|
||||
if len(parts) != 2 {
|
||||
return header
|
||||
}
|
||||
if strings.ToLower(parts[0]) != "bearer" {
|
||||
return header
|
||||
}
|
||||
return strings.TrimSpace(parts[1])
|
||||
}
|
||||
88
sdk/access/registry.go
Normal file
88
sdk/access/registry.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
)
|
||||
|
||||
// Provider validates credentials for incoming requests.
|
||||
type Provider interface {
|
||||
Identifier() string
|
||||
Authenticate(ctx context.Context, r *http.Request) (*Result, error)
|
||||
}
|
||||
|
||||
// Result conveys authentication outcome.
|
||||
type Result struct {
|
||||
Provider string
|
||||
Principal string
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
// ProviderFactory builds a provider from configuration data.
|
||||
type ProviderFactory func(cfg *config.AccessProvider, root *config.Config) (Provider, error)
|
||||
|
||||
var (
|
||||
registryMu sync.RWMutex
|
||||
registry = make(map[string]ProviderFactory)
|
||||
)
|
||||
|
||||
// RegisterProvider registers a provider factory for a given type identifier.
|
||||
func RegisterProvider(typ string, factory ProviderFactory) {
|
||||
if typ == "" || factory == nil {
|
||||
return
|
||||
}
|
||||
registryMu.Lock()
|
||||
registry[typ] = factory
|
||||
registryMu.Unlock()
|
||||
}
|
||||
|
||||
func buildProvider(cfg *config.AccessProvider, root *config.Config) (Provider, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("access: nil provider config")
|
||||
}
|
||||
registryMu.RLock()
|
||||
factory, ok := registry[cfg.Type]
|
||||
registryMu.RUnlock()
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("access: provider type %q is not registered", cfg.Type)
|
||||
}
|
||||
provider, err := factory(cfg, root)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("access: failed to build provider %q: %w", cfg.Name, err)
|
||||
}
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// BuildProviders constructs providers declared in configuration.
|
||||
func BuildProviders(root *config.Config) ([]Provider, error) {
|
||||
if root == nil {
|
||||
return nil, nil
|
||||
}
|
||||
providers := make([]Provider, 0, len(root.Access.Providers))
|
||||
for i := range root.Access.Providers {
|
||||
providerCfg := &root.Access.Providers[i]
|
||||
if providerCfg.Type == "" {
|
||||
continue
|
||||
}
|
||||
provider, err := buildProvider(providerCfg, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providers = append(providers, provider)
|
||||
}
|
||||
if len(providers) == 0 && len(root.APIKeys) > 0 {
|
||||
config.SyncInlineAPIKeys(root, root.APIKeys)
|
||||
if providerCfg := root.ConfigAPIKeyProvider(); providerCfg != nil {
|
||||
provider, err := buildProvider(providerCfg, root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providers = append(providers, provider)
|
||||
}
|
||||
}
|
||||
return providers, nil
|
||||
}
|
||||
Reference in New Issue
Block a user