Documentation
¶
Overview ¶
Package auth provides authentication and authorization primitives for tools.
It supports multiple authentication methods (JWT, API key, OAuth2 introspection) and role-based access control (RBAC). The package is protocol-agnostic and can be used with any transport layer.
Index ¶
- Variables
- func ConstantTimeCompare(a, b string) bool
- func GetHeader(ctx context.Context, key string) string
- func HashAPIKey(key string) string
- func HeadersFromContext(ctx context.Context) map[string][]string
- func PrincipalFromContext(ctx context.Context) string
- func TenantIDFromContext(ctx context.Context) string
- func WithAuthHeaders(next http.Handler) http.Handler
- func WithHeaders(ctx context.Context, headers map[string][]string) context.Context
- func WithIdentity(ctx context.Context, id *Identity) context.Context
- type APIKeyAuthenticator
- type APIKeyConfig
- type APIKeyInfo
- type APIKeyStore
- type AllowAllAuthorizer
- type AuthMethod
- type AuthRequest
- type AuthResult
- type Authenticator
- type AuthenticatorFactory
- type AuthenticatorFunc
- type Authorizer
- type AuthorizerFactory
- type AuthorizerFunc
- type AuthzError
- type AuthzRequest
- type CompositeAuthenticator
- type DenyAllAuthorizer
- type Identity
- type JWKSConfig
- type JWKSKeyProvider
- type JWTAuthenticator
- type JWTConfig
- type KeyProvider
- type MemoryAPIKeyStore
- type OAuth2Config
- type OAuth2IntrospectionAuthenticator
- type RBACConfig
- type Registry
- func (r *Registry) CreateAuthenticator(name string, cfg map[string]any) (Authenticator, error)
- func (r *Registry) CreateAuthorizer(name string, cfg map[string]any) (Authorizer, error)
- func (r *Registry) ListAuthenticators() []string
- func (r *Registry) ListAuthorizers() []string
- func (r *Registry) RegisterAuthenticator(name string, factory AuthenticatorFactory) error
- func (r *Registry) RegisterAuthorizer(name string, factory AuthorizerFactory) error
- type RoleConfig
- type SimpleRBACAuthorizer
- type StaticKeyProvider
Examples ¶
- AllowAllAuthorizer
- AnonymousIdentity
- AuthFailure
- AuthSuccess
- AuthzError
- CompositeAuthenticator.Authenticate
- DenyAllAuthorizer
- HashAPIKey
- Identity.HasPermission
- Identity.HasRole
- Identity.IsExpired
- IdentityFromContext
- NewAPIKeyAuthenticator
- NewAuthenticatorFunc
- NewCompositeAuthenticator
- NewJWTAuthenticator
- NewSimpleRBACAuthorizer
- PrincipalFromContext
- SimpleRBACAuthorizer.Authorize
- TenantIDFromContext
- WithIdentity
Constants ¶
This section is empty.
Variables ¶
var ( // Authentication errors ErrMissingCredentials = errors.New("auth: missing credentials") ErrInvalidCredentials = errors.New("auth: invalid credentials") ErrTokenExpired = errors.New("auth: token expired") ErrTokenMalformed = errors.New("auth: token malformed") ErrTokenInactive = errors.New("auth: token inactive") ErrIntrospectionFailed = errors.New("auth: introspection failed") ErrKeyNotFound = errors.New("auth: signing key not found") // Authorization errors ErrForbidden = errors.New("auth: access denied") )
Sentinel errors for authentication and authorization.
var DefaultRegistry = NewRegistry()
DefaultRegistry is the global auth registry with built-in factories.
Functions ¶
func ConstantTimeCompare ¶
ConstantTimeCompare performs constant-time comparison of two strings.
func GetHeader ¶
GetHeader retrieves a single header value from the context. Returns the first value if multiple values exist, or empty string if not found.
func HashAPIKey ¶
HashAPIKey hashes an API key using SHA-256 for storage.
Example ¶
package main
import (
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
// Hash an API key for storage
apiKey := "sk_live_abc123"
hash := auth.HashAPIKey(apiKey)
// Hash is deterministic
hash2 := auth.HashAPIKey(apiKey)
fmt.Println("Hashes match:", hash == hash2)
fmt.Println("Hash length:", len(hash)) // SHA-256 = 64 hex chars
}
Output: Hashes match: true Hash length: 64
func HeadersFromContext ¶
HeadersFromContext retrieves HTTP headers from the context. Returns nil if no headers are present.
func PrincipalFromContext ¶
PrincipalFromContext retrieves the principal from the context. Returns empty string if no identity is present.
Example ¶
package main
import (
"context"
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
identity := &auth.Identity{Principal: "alice@example.com"}
ctx := auth.WithIdentity(context.Background(), identity)
fmt.Println("Principal:", auth.PrincipalFromContext(ctx))
}
Output: Principal: alice@example.com
func TenantIDFromContext ¶
TenantIDFromContext retrieves the tenant ID from the context. Returns empty string if no identity is present or tenant is not set.
Example ¶
package main
import (
"context"
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
identity := &auth.Identity{
Principal: "alice",
TenantID: "acme-corp",
}
ctx := auth.WithIdentity(context.Background(), identity)
fmt.Println("Tenant:", auth.TenantIDFromContext(ctx))
}
Output: Tenant: acme-corp
func WithAuthHeaders ¶
WithAuthHeaders is HTTP middleware that extracts request headers into the context for use by authentication middleware.
This middleware should wrap HTTP handlers that process requests, enabling authenticators to access headers like Authorization and X-API-Key.
Usage:
mux.Handle("/api", auth.WithAuthHeaders(apiHandler))
func WithHeaders ¶
WithHeaders returns a new context with the given HTTP headers attached. These headers are used by authenticators to extract credentials.
func WithIdentity ¶
WithIdentity returns a new context with the given identity attached.
Example ¶
package main
import (
"context"
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
// Create an identity
identity := &auth.Identity{
Principal: "user@example.com",
TenantID: "tenant-123",
Roles: []string{"admin", "user"},
Method: auth.AuthMethodJWT,
}
// Attach to context
ctx := auth.WithIdentity(context.Background(), identity)
// Retrieve from context
retrieved := auth.IdentityFromContext(ctx)
fmt.Println("Principal:", retrieved.Principal)
fmt.Println("Tenant:", retrieved.TenantID)
}
Output: Principal: user@example.com Tenant: tenant-123
Types ¶
type APIKeyAuthenticator ¶
type APIKeyAuthenticator struct {
// contains filtered or unexported fields
}
APIKeyAuthenticator validates API keys.
func NewAPIKeyAuthenticator ¶
func NewAPIKeyAuthenticator(config APIKeyConfig, store APIKeyStore) *APIKeyAuthenticator
NewAPIKeyAuthenticator creates a new API key authenticator.
Example ¶
package main
import (
"context"
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
// Create an in-memory key store
store := auth.NewMemoryAPIKeyStore()
// Add an API key
keyHash := auth.HashAPIKey("sk_live_abc123")
_ = store.Add(&auth.APIKeyInfo{
ID: "key-1",
KeyHash: keyHash,
Principal: "user@example.com",
TenantID: "tenant-1",
Roles: []string{"admin"},
})
// Create authenticator
authenticator := auth.NewAPIKeyAuthenticator(auth.APIKeyConfig{
HeaderName: "X-API-Key",
}, store)
fmt.Println("Authenticator name:", authenticator.Name())
// Authenticate a request
ctx := context.Background()
req := &auth.AuthRequest{
Headers: map[string][]string{
"X-API-Key": {"sk_live_abc123"},
},
}
result, err := authenticator.Authenticate(ctx, req)
if err == nil && result.Authenticated {
fmt.Println("Principal:", result.Identity.Principal)
fmt.Println("Tenant:", result.Identity.TenantID)
}
}
Output: Authenticator name: api_key Principal: user@example.com Tenant: tenant-1
func (*APIKeyAuthenticator) Authenticate ¶
func (a *APIKeyAuthenticator) Authenticate(ctx context.Context, req *AuthRequest) (*AuthResult, error)
Authenticate validates the API key.
func (*APIKeyAuthenticator) Name ¶
func (a *APIKeyAuthenticator) Name() string
Name returns "api_key".
func (*APIKeyAuthenticator) Supports ¶
func (a *APIKeyAuthenticator) Supports(_ context.Context, req *AuthRequest) bool
Supports returns true if the request contains an API key header.
type APIKeyConfig ¶
type APIKeyConfig struct {
// HeaderName is the header containing the API key.
// Default: "X-API-Key"
HeaderName string
// HashAlgorithm is the algorithm used to hash stored keys.
// Options: "sha256" (default), "plain" (not recommended)
HashAlgorithm string
}
APIKeyConfig configures the API key authenticator.
type APIKeyInfo ¶
type APIKeyInfo struct {
// ID is a unique identifier for this key.
ID string
// KeyHash is the hashed API key (SHA-256 hex).
KeyHash string
// Principal is the identity associated with this key.
Principal string
// TenantID is the tenant this key belongs to.
TenantID string
// Roles are the roles granted to this key.
Roles []string
// ExpiresAt is when this key expires (zero = never).
ExpiresAt time.Time
// Metadata contains additional key metadata.
Metadata map[string]any
}
APIKeyInfo contains information about a registered API key.
type APIKeyStore ¶
type APIKeyStore interface {
// Lookup retrieves an API key by its hash.
// Returns nil if not found.
Lookup(ctx context.Context, keyHash string) (*APIKeyInfo, error)
}
APIKeyStore provides storage for API keys.
type AllowAllAuthorizer ¶
type AllowAllAuthorizer struct{}
AllowAllAuthorizer permits all requests.
Example ¶
package main
import (
"context"
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
authz := auth.AllowAllAuthorizer{}
ctx := context.Background()
req := &auth.AuthzRequest{
Subject: &auth.Identity{Principal: "anyone"},
Resource: "tool:anything",
Action: "call",
}
err := authz.Authorize(ctx, req)
fmt.Println("Allowed:", err == nil)
fmt.Println("Name:", authz.Name())
}
Output: Allowed: true Name: allow_all
func (AllowAllAuthorizer) Authorize ¶
func (a AllowAllAuthorizer) Authorize(_ context.Context, _ *AuthzRequest) error
Authorize always returns nil (permitted).
func (AllowAllAuthorizer) Name ¶
func (a AllowAllAuthorizer) Name() string
Name returns "allow_all".
type AuthMethod ¶
type AuthMethod string
AuthMethod indicates how authentication was performed.
const ( AuthMethodNone AuthMethod = "none" AuthMethodJWT AuthMethod = "jwt" AuthMethodAPIKey AuthMethod = "api_key" AuthMethodOAuth2 AuthMethod = "oauth2" AuthMethodBasic AuthMethod = "basic" AuthMethodAnonymous AuthMethod = "anonymous" AuthMethodComposite AuthMethod = "composite" )
type AuthRequest ¶
type AuthRequest struct {
// Headers contains HTTP headers (Authorization, X-API-Key, etc.)
Headers map[string][]string
// Resource is the target resource (optional, for context).
Resource string
// Metadata contains additional request metadata.
Metadata map[string]any
}
AuthRequest contains the information needed for authentication.
func (*AuthRequest) GetHeader ¶
func (r *AuthRequest) GetHeader(key string) string
GetHeader returns the first value for a header, or empty string.
type AuthResult ¶
type AuthResult struct {
// Authenticated is true if authentication succeeded.
Authenticated bool
// Identity is the authenticated identity (only if Authenticated=true).
Identity *Identity
// Error is the authentication error (only if Authenticated=false).
Error error
// Method indicates which authenticator method was used.
Method string
}
AuthResult is the result of an authentication attempt.
func AuthFailure ¶
func AuthFailure(err error, method string) *AuthResult
AuthFailure creates a failed authentication result.
Example ¶
package main
import (
"errors"
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
result := auth.AuthFailure(auth.ErrInvalidCredentials, "jwt")
fmt.Println("Authenticated:", result.Authenticated)
fmt.Println("Method:", result.Method)
fmt.Println("Error is invalid credentials:", errors.Is(result.Error, auth.ErrInvalidCredentials))
}
Output: Authenticated: false Method: jwt Error is invalid credentials: true
func AuthSuccess ¶
func AuthSuccess(identity *Identity) *AuthResult
AuthSuccess creates a successful authentication result.
Example ¶
package main
import (
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
identity := &auth.Identity{
Principal: "alice",
Method: auth.AuthMethodAPIKey,
}
result := auth.AuthSuccess(identity)
fmt.Println("Authenticated:", result.Authenticated)
fmt.Println("Method:", result.Method)
fmt.Println("Has error:", result.Error != nil)
}
Output: Authenticated: true Method: api_key Has error: false
type Authenticator ¶
type Authenticator interface {
// Name returns a unique identifier for this authenticator.
// Must be constant for the lifetime of the authenticator.
Name() string
// Supports returns true if this authenticator can handle the request.
// Should be a fast check (e.g., header presence) without network calls.
Supports(ctx context.Context, req *AuthRequest) bool
// Authenticate validates credentials and returns a result.
// Returns (result, nil) for success/failure, (nil, error) for internal errors.
Authenticate(ctx context.Context, req *AuthRequest) (*AuthResult, error)
}
Authenticator validates credentials and returns an identity.
Contract ¶
Concurrency:
- Implementations must be safe for concurrent use from multiple goroutines.
- Internal state (if any) must be protected by appropriate synchronization.
Context:
- All methods accepting context.Context must honor cancellation and deadlines.
- Long-running operations (e.g., remote token introspection) should check ctx.Done().
Errors:
- Authenticate returns (nil, error) ONLY for internal/infrastructure errors (e.g., network failure, database unavailable).
- Authentication failures (invalid token, expired, etc.) return (*AuthResult, nil) with result.Authenticated=false and result.Error set to the auth error.
- Use sentinel errors from this package for auth failures: ErrInvalidCredentials, ErrTokenExpired, ErrTokenMalformed, ErrMissingCredentials.
Ownership:
- The caller owns the AuthRequest; implementations must not modify it.
- The returned AuthResult is owned by the caller.
type AuthenticatorFactory ¶
type AuthenticatorFactory func(cfg map[string]any) (Authenticator, error)
AuthenticatorFactory creates an authenticator from configuration.
type AuthenticatorFunc ¶
type AuthenticatorFunc struct {
// contains filtered or unexported fields
}
AuthenticatorFunc is an adapter to allow use of ordinary functions as Authenticators.
func NewAuthenticatorFunc ¶
func NewAuthenticatorFunc( name string, supports func(ctx context.Context, req *AuthRequest) bool, auth func(ctx context.Context, req *AuthRequest) (*AuthResult, error), ) *AuthenticatorFunc
NewAuthenticatorFunc creates an AuthenticatorFunc.
Example ¶
package main
import (
"context"
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
// Create a custom authenticator using a function
customAuth := auth.NewAuthenticatorFunc(
"custom",
func(ctx context.Context, req *auth.AuthRequest) bool {
// Support requests with X-Custom-Auth header
return req.GetHeader("X-Custom-Auth") != ""
},
func(ctx context.Context, req *auth.AuthRequest) (*auth.AuthResult, error) {
token := req.GetHeader("X-Custom-Auth")
if token == "valid-token" {
return auth.AuthSuccess(&auth.Identity{
Principal: "custom-user",
Method: "custom",
}), nil
}
return auth.AuthFailure(auth.ErrInvalidCredentials, "custom"), nil
},
)
fmt.Println("Authenticator name:", customAuth.Name())
ctx := context.Background()
req := &auth.AuthRequest{
Headers: map[string][]string{
"X-Custom-Auth": {"valid-token"},
},
}
result, _ := customAuth.Authenticate(ctx, req)
fmt.Println("Authenticated:", result.Authenticated)
}
Output: Authenticator name: custom Authenticated: true
func (*AuthenticatorFunc) Authenticate ¶
func (f *AuthenticatorFunc) Authenticate(ctx context.Context, req *AuthRequest) (*AuthResult, error)
Authenticate validates credentials.
func (*AuthenticatorFunc) Name ¶
func (f *AuthenticatorFunc) Name() string
Name returns the authenticator name.
func (*AuthenticatorFunc) Supports ¶
func (f *AuthenticatorFunc) Supports(ctx context.Context, req *AuthRequest) bool
Supports returns true if this authenticator can handle the request.
type Authorizer ¶
type Authorizer interface {
// Authorize checks if the request is permitted.
// Returns nil if authorized, or an error (typically *AuthzError) if denied.
Authorize(ctx context.Context, req *AuthzRequest) error
// Name returns a unique identifier for this authorizer.
// Must be constant for the lifetime of the authorizer.
Name() string
}
Authorizer determines if an identity is allowed to perform an action.
Contract ¶
Concurrency:
- Implementations must be safe for concurrent use from multiple goroutines.
- Policy state (roles, permissions) should be immutable after construction or protected by appropriate synchronization.
Context:
- Authorize should honor context cancellation for external policy lookups.
- For in-memory policies, context is typically unused but must be accepted.
Errors:
- Returns nil if the request is authorized.
- Returns *AuthzError (which Is ErrForbidden) if access is denied.
- Returns other errors only for internal failures (policy unavailable, etc.).
Ownership:
- The caller owns the AuthzRequest; implementations must not modify it.
- The AuthzRequest.Subject identity is read-only.
type AuthorizerFactory ¶
type AuthorizerFactory func(cfg map[string]any) (Authorizer, error)
AuthorizerFactory creates an authorizer from configuration.
type AuthorizerFunc ¶
type AuthorizerFunc func(ctx context.Context, req *AuthzRequest) error
AuthorizerFunc is an adapter to allow use of ordinary functions as Authorizers.
func (AuthorizerFunc) Authorize ¶
func (f AuthorizerFunc) Authorize(ctx context.Context, req *AuthzRequest) error
Authorize calls the function.
func (AuthorizerFunc) Name ¶
func (f AuthorizerFunc) Name() string
Name returns "func" for function-based authorizers.
type AuthzError ¶
type AuthzError struct {
// Subject is the identity that was denied.
Subject string
// Resource is the resource that was denied access to.
Resource string
// Action is the action that was denied.
Action string
// Reason explains why access was denied.
Reason string
// Cause is the underlying error if any.
Cause error
}
AuthzError represents an authorization failure.
Example ¶
package main
import (
"errors"
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
err := &auth.AuthzError{
Subject: "alice",
Resource: "tool:admin_panel",
Action: "access",
Reason: "insufficient permissions",
}
fmt.Println("Is forbidden:", errors.Is(err, auth.ErrForbidden))
}
Output: Is forbidden: true
func (*AuthzError) Is ¶
func (e *AuthzError) Is(target error) bool
Is reports whether this error matches the target.
func (*AuthzError) Unwrap ¶
func (e *AuthzError) Unwrap() error
Unwrap returns the cause error for errors.Is/As support.
type AuthzRequest ¶
type AuthzRequest struct {
// Subject is the identity making the request.
Subject *Identity
// Resource is the target resource (e.g., "tool:search_tools").
Resource string
// Action is the requested action (e.g., "call", "list").
Action string
// ResourceType categorizes the resource (e.g., "tool", "namespace").
ResourceType string
}
AuthzRequest contains the information needed for authorization.
func (*AuthzRequest) ToolName ¶
func (r *AuthzRequest) ToolName() string
ToolName extracts the tool name from the resource. Removes "tool:" prefix if present.
type CompositeAuthenticator ¶
type CompositeAuthenticator struct {
// Authenticators is the ordered list of authenticators to try.
Authenticators []Authenticator
// StopOnFirst stops on the first successful authentication.
// Default: true
StopOnFirst bool
}
CompositeAuthenticator tries multiple authenticators in sequence. It returns on the first successful authentication or after all fail.
func NewCompositeAuthenticator ¶
func NewCompositeAuthenticator(auths ...Authenticator) *CompositeAuthenticator
NewCompositeAuthenticator creates a composite authenticator.
Example ¶
package main
import (
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
// Create individual authenticators
jwtAuth := auth.NewJWTAuthenticator(
auth.JWTConfig{Issuer: "issuer"},
auth.NewStaticKeyProvider([]byte("secret")),
)
store := auth.NewMemoryAPIKeyStore()
apiKeyAuth := auth.NewAPIKeyAuthenticator(
auth.APIKeyConfig{HeaderName: "X-API-Key"},
store,
)
// Combine them
composite := auth.NewCompositeAuthenticator(jwtAuth, apiKeyAuth)
fmt.Println("Authenticator name:", composite.Name())
fmt.Println("Number of authenticators:", len(composite.Authenticators))
}
Output: Authenticator name: composite Number of authenticators: 2
func (*CompositeAuthenticator) Authenticate ¶
func (c *CompositeAuthenticator) Authenticate(ctx context.Context, req *AuthRequest) (*AuthResult, error)
Authenticate tries each authenticator in sequence.
Example ¶
package main
import (
"context"
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
// Create a composite that tries API key first, then JWT
store := auth.NewMemoryAPIKeyStore()
hash := auth.HashAPIKey("valid-key")
_ = store.Add(&auth.APIKeyInfo{
ID: "key-1",
KeyHash: hash,
Principal: "api-user",
})
apiKeyAuth := auth.NewAPIKeyAuthenticator(
auth.APIKeyConfig{HeaderName: "X-API-Key"},
store,
)
jwtAuth := auth.NewJWTAuthenticator(
auth.JWTConfig{},
auth.NewStaticKeyProvider([]byte("secret")),
)
composite := auth.NewCompositeAuthenticator(apiKeyAuth, jwtAuth)
// Request with API key
ctx := context.Background()
req := &auth.AuthRequest{
Headers: map[string][]string{
"X-API-Key": {"valid-key"},
},
}
result, err := composite.Authenticate(ctx, req)
if err == nil && result.Authenticated {
fmt.Println("Method:", result.Method)
fmt.Println("Principal:", result.Identity.Principal)
}
}
Output: Method: api_key Principal: api-user
func (*CompositeAuthenticator) Name ¶
func (c *CompositeAuthenticator) Name() string
Name returns "composite".
func (*CompositeAuthenticator) Supports ¶
func (c *CompositeAuthenticator) Supports(ctx context.Context, req *AuthRequest) bool
Supports returns true if any authenticator supports the request.
type DenyAllAuthorizer ¶
type DenyAllAuthorizer struct{}
DenyAllAuthorizer denies all requests.
Example ¶
package main
import (
"context"
"errors"
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
authz := auth.DenyAllAuthorizer{}
ctx := context.Background()
req := &auth.AuthzRequest{
Subject: &auth.Identity{Principal: "anyone"},
Resource: "tool:anything",
Action: "call",
}
err := authz.Authorize(ctx, req)
fmt.Println("Denied:", err != nil)
fmt.Println("Is forbidden:", errors.Is(err, auth.ErrForbidden))
fmt.Println("Name:", authz.Name())
}
Output: Denied: true Is forbidden: true Name: deny_all
func (DenyAllAuthorizer) Authorize ¶
func (a DenyAllAuthorizer) Authorize(_ context.Context, req *AuthzRequest) error
Authorize always returns an error (denied).
type Identity ¶
type Identity struct {
// Principal is the unique identifier (e.g., user ID, email).
Principal string
// TenantID is the tenant this identity belongs to (multi-tenancy).
TenantID string
// Roles are the roles assigned to this identity.
Roles []string
// Permissions are explicit permissions granted to this identity.
Permissions []string
// Method indicates how authentication was performed.
Method AuthMethod
// Claims contains the raw claims from the token.
Claims map[string]any
// ExpiresAt is when this identity expires.
ExpiresAt time.Time
// IssuedAt is when this identity was created.
IssuedAt time.Time
}
Identity represents an authenticated principal.
func AnonymousIdentity ¶
func AnonymousIdentity() *Identity
AnonymousIdentity creates a default anonymous identity.
Example ¶
package main
import (
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
anon := auth.AnonymousIdentity()
fmt.Println("Principal:", anon.Principal)
fmt.Println("Method:", anon.Method)
fmt.Println("Is anonymous:", anon.IsAnonymous())
}
Output: Principal: anonymous Method: anonymous Is anonymous: true
func IdentityFromContext ¶
IdentityFromContext retrieves the identity from the context. Returns nil if no identity is present.
Example ¶
package main
import (
"context"
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
// Context with identity
identity := &auth.Identity{Principal: "alice"}
ctx := auth.WithIdentity(context.Background(), identity)
fmt.Println("With identity:", auth.IdentityFromContext(ctx) != nil)
// Context without identity
emptyCtx := context.Background()
fmt.Println("Without identity:", auth.IdentityFromContext(emptyCtx) == nil)
}
Output: With identity: true Without identity: true
func (*Identity) HasPermission ¶
HasPermission checks if the identity has a specific permission.
Example ¶
package main
import (
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
identity := &auth.Identity{
Principal: "alice",
Permissions: []string{"tool:read", "tool:write"},
}
fmt.Println("Has tool:read:", identity.HasPermission("tool:read"))
fmt.Println("Has tool:delete:", identity.HasPermission("tool:delete"))
}
Output: Has tool:read: true Has tool:delete: false
func (*Identity) HasRole ¶
HasRole checks if the identity has a specific role.
Example ¶
package main
import (
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
identity := &auth.Identity{
Principal: "alice",
Roles: []string{"admin", "user"},
}
fmt.Println("Has admin:", identity.HasRole("admin"))
fmt.Println("Has guest:", identity.HasRole("guest"))
}
Output: Has admin: true Has guest: false
func (*Identity) IsAnonymous ¶
IsAnonymous returns true if this is an anonymous identity.
func (*Identity) IsExpired ¶
IsExpired checks if the identity has expired.
Example ¶
package main
import (
"fmt"
"time"
"github.com/jonwraymond/toolops/auth"
)
func main() {
// Non-expiring identity
noExpiry := &auth.Identity{Principal: "alice"}
fmt.Println("No expiry is expired:", noExpiry.IsExpired())
// Future expiry
future := &auth.Identity{
Principal: "bob",
ExpiresAt: time.Now().Add(time.Hour),
}
fmt.Println("Future expiry is expired:", future.IsExpired())
// Past expiry
past := &auth.Identity{
Principal: "charlie",
ExpiresAt: time.Now().Add(-time.Hour),
}
fmt.Println("Past expiry is expired:", past.IsExpired())
}
Output: No expiry is expired: false Future expiry is expired: false Past expiry is expired: true
type JWKSConfig ¶
type JWKSConfig struct {
// URL is the JWKS endpoint URL.
URL string
// CacheTTL is how long to cache keys before refreshing.
// Default: 1 hour
CacheTTL time.Duration
// HTTPClient is the HTTP client to use for requests.
// If nil, a default client with 30s timeout is used.
HTTPClient *http.Client
}
JWKSConfig configures the JWKS key provider.
type JWKSKeyProvider ¶
type JWKSKeyProvider struct {
// contains filtered or unexported fields
}
JWKSKeyProvider retrieves signing keys from a JWKS endpoint. It implements the KeyProvider interface with caching support.
func NewJWKSKeyProvider ¶
func NewJWKSKeyProvider(config JWKSConfig) *JWKSKeyProvider
NewJWKSKeyProvider creates a new JWKS key provider.
type JWTAuthenticator ¶
type JWTAuthenticator struct {
// contains filtered or unexported fields
}
JWTAuthenticator validates JWT tokens.
func NewJWTAuthenticator ¶
func NewJWTAuthenticator(config JWTConfig, keyProvider KeyProvider) *JWTAuthenticator
NewJWTAuthenticator creates a new JWT authenticator.
Example ¶
package main
import (
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
// Create a JWT authenticator with static key
keyProvider := auth.NewStaticKeyProvider([]byte("my-secret-key"))
authenticator := auth.NewJWTAuthenticator(auth.JWTConfig{
Issuer: "https://example.com",
Audience: "my-api",
PrincipalClaim: "sub",
RolesClaim: "roles",
}, keyProvider)
fmt.Println("Authenticator name:", authenticator.Name())
}
Output: Authenticator name: jwt
func (*JWTAuthenticator) Authenticate ¶
func (a *JWTAuthenticator) Authenticate(ctx context.Context, req *AuthRequest) (*AuthResult, error)
Authenticate validates the JWT token.
func (*JWTAuthenticator) Supports ¶
func (a *JWTAuthenticator) Supports(_ context.Context, req *AuthRequest) bool
Supports returns true if the request contains a JWT token.
type JWTConfig ¶
type JWTConfig struct {
// Issuer is the expected token issuer (iss claim).
Issuer string
// Audience is the expected token audience (aud claim).
Audience string
// HeaderName is the header containing the token.
// Default: "Authorization"
HeaderName string
// TokenPrefix is the prefix before the token in the header.
// Default: "Bearer "
TokenPrefix string
// PrincipalClaim is the claim containing the user principal.
// Default: "sub"
PrincipalClaim string
// TenantClaim is the claim containing the tenant ID.
TenantClaim string
// RolesClaim is the claim containing user roles.
RolesClaim string
}
JWTConfig configures the JWT authenticator.
type KeyProvider ¶
type KeyProvider interface {
// GetKey returns the key for the given key ID.
GetKey(ctx context.Context, keyID string) (any, error)
}
KeyProvider retrieves signing keys for JWT validation.
type MemoryAPIKeyStore ¶
type MemoryAPIKeyStore struct {
// contains filtered or unexported fields
}
MemoryAPIKeyStore is an in-memory API key store.
func NewMemoryAPIKeyStore ¶
func NewMemoryAPIKeyStore() *MemoryAPIKeyStore
NewMemoryAPIKeyStore creates a new in-memory API key store.
func (*MemoryAPIKeyStore) Add ¶
func (s *MemoryAPIKeyStore) Add(info *APIKeyInfo) error
Add adds an API key to the store.
func (*MemoryAPIKeyStore) Lookup ¶
func (s *MemoryAPIKeyStore) Lookup(_ context.Context, keyHash string) (*APIKeyInfo, error)
Lookup retrieves an API key by its hash.
func (*MemoryAPIKeyStore) Remove ¶
func (s *MemoryAPIKeyStore) Remove(keyHash string) error
Remove removes an API key from the store.
type OAuth2Config ¶
type OAuth2Config struct {
// IntrospectionEndpoint is the URL of the OAuth2 introspection endpoint.
IntrospectionEndpoint string
// ClientID is the client identifier for introspection requests.
ClientID string
// ClientSecret is the client secret for introspection requests.
ClientSecret string
// ClientAuthMethod is how to authenticate to the introspection endpoint.
// Options: "client_secret_basic" (default), "client_secret_post"
ClientAuthMethod string
// CacheTTL is how long to cache positive introspection results.
// Default: 5 minutes. Set to 0 to disable caching.
CacheTTL time.Duration
// Timeout is the HTTP request timeout for introspection calls.
// Default: 10 seconds.
Timeout time.Duration
// PrincipalClaim is the claim containing the user principal.
// Default: "sub"
PrincipalClaim string
// TenantClaim is the claim containing the tenant ID.
TenantClaim string
// RolesClaim is the claim containing user roles.
RolesClaim string
// ScopesClaim is the claim containing OAuth2 scopes.
// Default: "scope" (space-separated string)
ScopesClaim string
// HTTPClient is the HTTP client to use. If nil, a default client is used.
HTTPClient *http.Client
}
OAuth2Config configures the OAuth2 token introspection authenticator.
type OAuth2IntrospectionAuthenticator ¶
type OAuth2IntrospectionAuthenticator struct {
// contains filtered or unexported fields
}
OAuth2IntrospectionAuthenticator validates OAuth2 tokens via introspection.
func NewOAuth2IntrospectionAuthenticator ¶
func NewOAuth2IntrospectionAuthenticator(config OAuth2Config) *OAuth2IntrospectionAuthenticator
NewOAuth2IntrospectionAuthenticator creates a new OAuth2 introspection authenticator.
func (*OAuth2IntrospectionAuthenticator) Authenticate ¶
func (a *OAuth2IntrospectionAuthenticator) Authenticate(ctx context.Context, req *AuthRequest) (*AuthResult, error)
Authenticate validates the token via introspection.
func (*OAuth2IntrospectionAuthenticator) Name ¶
func (a *OAuth2IntrospectionAuthenticator) Name() string
Name returns "oauth2_introspection".
func (*OAuth2IntrospectionAuthenticator) Supports ¶
func (a *OAuth2IntrospectionAuthenticator) Supports(_ context.Context, req *AuthRequest) bool
Supports returns true if the request contains a Bearer token.
type RBACConfig ¶
type RBACConfig struct {
// Roles defines role configurations.
Roles map[string]RoleConfig
// DefaultRole is assigned to identities without explicit roles.
DefaultRole string
}
RBACConfig configures the simple RBAC authorizer.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry manages authenticator and authorizer factories.
func (*Registry) CreateAuthenticator ¶
CreateAuthenticator instantiates an authenticator by name.
func (*Registry) CreateAuthorizer ¶
CreateAuthorizer instantiates an authorizer by name.
func (*Registry) ListAuthenticators ¶
ListAuthenticators returns registered authenticator names.
func (*Registry) ListAuthorizers ¶
ListAuthorizers returns registered authorizer names.
func (*Registry) RegisterAuthenticator ¶
func (r *Registry) RegisterAuthenticator(name string, factory AuthenticatorFactory) error
RegisterAuthenticator adds an authenticator factory.
func (*Registry) RegisterAuthorizer ¶
func (r *Registry) RegisterAuthorizer(name string, factory AuthorizerFactory) error
RegisterAuthorizer adds an authorizer factory.
type RoleConfig ¶
type RoleConfig struct {
// Permissions are explicit permission strings (e.g., "tool:*:call").
Permissions []string
// Inherits lists roles this role inherits from.
Inherits []string
// AllowedTools is a list of tool names this role can access.
AllowedTools []string
// DeniedTools is a list of tool names this role cannot access.
DeniedTools []string
// AllowedActions is a list of actions this role can perform.
AllowedActions []string
}
RoleConfig defines permissions for a role.
type SimpleRBACAuthorizer ¶
type SimpleRBACAuthorizer struct {
// contains filtered or unexported fields
}
SimpleRBACAuthorizer provides simple role-based access control.
func NewSimpleRBACAuthorizer ¶
func NewSimpleRBACAuthorizer(config RBACConfig) *SimpleRBACAuthorizer
NewSimpleRBACAuthorizer creates a new simple RBAC authorizer.
Example ¶
package main
import (
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
// Define roles and permissions
rbac := auth.NewSimpleRBACAuthorizer(auth.RBACConfig{
Roles: map[string]auth.RoleConfig{
"admin": {
AllowedTools: []string{"*"},
AllowedActions: []string{"*"},
},
"reader": {
AllowedTools: []string{"search_*", "list_*", "get_*"},
AllowedActions: []string{"call"},
},
},
DefaultRole: "reader",
})
fmt.Println("Authorizer name:", rbac.Name())
}
Output: Authorizer name: simple_rbac
func (*SimpleRBACAuthorizer) Authorize ¶
func (a *SimpleRBACAuthorizer) Authorize(_ context.Context, req *AuthzRequest) error
Authorize checks if the identity is allowed to perform the action.
Example ¶
package main
import (
"context"
"fmt"
"github.com/jonwraymond/toolops/auth"
)
func main() {
rbac := auth.NewSimpleRBACAuthorizer(auth.RBACConfig{
Roles: map[string]auth.RoleConfig{
"admin": {
AllowedTools: []string{"*"},
},
"user": {
AllowedTools: []string{"read_*", "search_*"},
DeniedTools: []string{"admin_*"}, // Deny tools starting with admin_
},
},
})
ctx := context.Background()
// Admin can access anything
adminReq := &auth.AuthzRequest{
Subject: &auth.Identity{Principal: "admin", Roles: []string{"admin"}},
Resource: "tool:delete_user",
Action: "call",
}
fmt.Println("Admin delete_user:", rbac.Authorize(ctx, adminReq) == nil)
// User can read
userReq := &auth.AuthzRequest{
Subject: &auth.Identity{Principal: "user1", Roles: []string{"user"}},
Resource: "tool:read_file",
Action: "call",
}
fmt.Println("User read_file:", rbac.Authorize(ctx, userReq) == nil)
// User cannot access admin tools (denied by prefix pattern)
userAdminReq := &auth.AuthzRequest{
Subject: &auth.Identity{Principal: "user1", Roles: []string{"user"}},
Resource: "tool:admin_panel",
Action: "call",
}
fmt.Println("User admin_panel:", rbac.Authorize(ctx, userAdminReq) == nil)
}
Output: Admin delete_user: true User read_file: true User admin_panel: false
func (*SimpleRBACAuthorizer) Name ¶
func (a *SimpleRBACAuthorizer) Name() string
Name returns "simple_rbac".
type StaticKeyProvider ¶
type StaticKeyProvider struct {
// contains filtered or unexported fields
}
StaticKeyProvider provides a static signing key.
func NewStaticKeyProvider ¶
func NewStaticKeyProvider(key []byte) *StaticKeyProvider
NewStaticKeyProvider creates a static key provider.