auth

package
v0.1.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 6, 2026 License: MIT Imports: 18 Imported by: 0

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

Examples

Constants

This section is empty.

Variables

View Source
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.

View Source
var DefaultRegistry = NewRegistry()

DefaultRegistry is the global auth registry with built-in factories.

Functions

func ConstantTimeCompare

func ConstantTimeCompare(a, b string) bool

ConstantTimeCompare performs constant-time comparison of two strings.

func GetHeader

func GetHeader(ctx context.Context, key string) string

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

func HashAPIKey(key string) string

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

func HeadersFromContext(ctx context.Context) map[string][]string

HeadersFromContext retrieves HTTP headers from the context. Returns nil if no headers are present.

func PrincipalFromContext

func PrincipalFromContext(ctx context.Context) string

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

func TenantIDFromContext(ctx context.Context) string

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

func WithAuthHeaders(next http.Handler) http.Handler

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

func WithHeaders(ctx context.Context, headers map[string][]string) context.Context

WithHeaders returns a new context with the given HTTP headers attached. These headers are used by authenticators to extract credentials.

func WithIdentity

func WithIdentity(ctx context.Context, id *Identity) context.Context

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

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) Error

func (e *AuthzError) Error() string

Error returns the error message.

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).

func (DenyAllAuthorizer) Name

func (a DenyAllAuthorizer) Name() string

Name returns "deny_all".

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

func IdentityFromContext(ctx context.Context) *Identity

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

func (id *Identity) HasPermission(perm string) bool

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

func (id *Identity) HasRole(role string) bool

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

func (id *Identity) IsAnonymous() bool

IsAnonymous returns true if this is an anonymous identity.

func (*Identity) IsExpired

func (id *Identity) IsExpired() bool

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.

func (*JWKSKeyProvider) GetKey

func (p *JWKSKeyProvider) GetKey(ctx context.Context, keyID string) (any, error)

GetKey returns the key for the given key ID. If keyID is empty and there's exactly one key, that key is returned.

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) Name

func (a *JWTAuthenticator) Name() string

Name returns "jwt".

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

Authenticate validates the token via introspection.

func (*OAuth2IntrospectionAuthenticator) Name

Name returns "oauth2_introspection".

func (*OAuth2IntrospectionAuthenticator) Supports

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 NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new auth registry.

func (*Registry) CreateAuthenticator

func (r *Registry) CreateAuthenticator(name string, cfg map[string]any) (Authenticator, error)

CreateAuthenticator instantiates an authenticator by name.

func (*Registry) CreateAuthorizer

func (r *Registry) CreateAuthorizer(name string, cfg map[string]any) (Authorizer, error)

CreateAuthorizer instantiates an authorizer by name.

func (*Registry) ListAuthenticators

func (r *Registry) ListAuthenticators() []string

ListAuthenticators returns registered authenticator names.

func (*Registry) ListAuthorizers

func (r *Registry) ListAuthorizers() []string

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.

func (*StaticKeyProvider) GetKey

func (p *StaticKeyProvider) GetKey(_ context.Context, _ string) (any, error)

GetKey returns the static key.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL