Documentation
¶
Overview ¶
Package auth provides authentication and RBAC for ChronosOS.
Index ¶
- func APIKeyMiddleware(cfg APIKeyConfig) func(http.Handler) http.Handler
- func CreateTestToken(claims UserClaims, secret string) string
- func HashKey(rawKey string) string
- func JWTMiddleware(cfg JWTConfig) func(http.Handler) http.Handler
- func RequireRole(svc *Service, required Role) func(http.Handler) http.Handler
- func WithUser(ctx context.Context, claims *UserClaims) context.Context
- type APIKeyConfig
- type APIKeyEntry
- type Audience
- type JWTConfig
- type KeyRecord
- type KeyStore
- type MemoryKeyStore
- func (s *MemoryKeyStore) Add(_ context.Context, rawKey string, meta KeyRecord) (KeyRecord, error)
- func (s *MemoryKeyStore) List(_ context.Context) ([]KeyRecord, error)
- func (s *MemoryKeyStore) Lookup(_ context.Context, rawKey string) (*KeyRecord, bool, error)
- func (s *MemoryKeyStore) Remove(_ context.Context, id string) error
- type MemoryQuotaStore
- type Quota
- type QuotaStore
- type Role
- type Service
- type Usage
- type UserClaims
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func APIKeyMiddleware ¶ added in v0.3.0
func APIKeyMiddleware(cfg APIKeyConfig) func(http.Handler) http.Handler
APIKeyMiddleware returns HTTP middleware that validates API keys from the configured header (default: X-Api-Key).
Keys are matched against a hashed KeyStore using constant-time comparison; raw key material is never retained in the middleware. When Quotas is set, per-key and per-tenant limits are enforced (HTTP 429 on breach).
func CreateTestToken ¶ added in v0.3.0
func CreateTestToken(claims UserClaims, secret string) string
CreateTestToken creates a simple HMAC-SHA256 signed JWT for testing.
func HashKey ¶ added in v0.5.0
HashKey returns the hex-encoded SHA-256 digest of a raw API key. SHA-256 is appropriate here because API keys are high-entropy secrets (unlike low-entropy passwords, which would warrant bcrypt/argon2).
func JWTMiddleware ¶ added in v0.3.0
JWTMiddleware returns HTTP middleware that validates JWT bearer tokens.
func RequireRole ¶ added in v0.3.0
RequireRole returns middleware that checks the authenticated user has at least the specified role. Must be used after JWT or API key middleware.
Types ¶
type APIKeyConfig ¶ added in v0.3.0
type APIKeyConfig struct {
// HeaderName is the request header carrying the key (default: X-Api-Key).
HeaderName string
// Keys is the legacy inline key map. Raw keys are hashed at construction.
Keys map[string]APIKeyEntry
// Store is an optional persisted, hashed key store. When set it is used
// instead of Keys.
Store KeyStore
// SkipPaths bypass authentication entirely.
SkipPaths []string
// Quotas, when set, enforces per-key and per-tenant quotas on each request.
Quotas QuotaStore
// TenantQuotas maps a tenant ID to its quota, enforced when Quotas is set.
TenantQuotas map[string]Quota
}
APIKeyConfig holds configuration for API key authentication.
Backward compatibility: the legacy Keys map (raw key -> entry) is still accepted. At middleware construction the raw keys are hashed into an in-memory KeyStore and the plaintext is not retained. For a persisted, pre-hashed store, set Store instead (it takes precedence over Keys).
type APIKeyEntry ¶ added in v0.3.0
APIKeyEntry represents a configured API key with associated metadata.
type Audience ¶ added in v0.5.0
type Audience []string
Audience represents a JWT "aud" claim which may be encoded either as a single string or as an array of strings. It normalizes both forms to a slice.
func (Audience) Contains ¶ added in v0.5.0
Contains reports whether the audience set includes the given value.
func (Audience) MarshalJSON ¶ added in v0.5.0
MarshalJSON emits a single string when there is exactly one audience, otherwise a JSON array.
func (*Audience) UnmarshalJSON ¶ added in v0.5.0
UnmarshalJSON accepts both a JSON string and a JSON array of strings.
type JWTConfig ¶ added in v0.3.0
type JWTConfig struct {
// Secret is the shared HMAC secret for HS256 tokens. Required to accept
// HS256 tokens; leave empty to reject them.
Secret string
// Issuer, when set, is enforced against the token "iss" claim.
Issuer string
// Audience, when set, is enforced against the token "aud" claim.
Audience string
// SkipPaths are request paths that bypass authentication entirely.
SkipPaths []string
// AllowExpiredAt is the grace window during which an already-expired token
// is still accepted, but ONLY on paths listed in AllowExpiredPaths (i.e.
// explicit token-refresh endpoints). It has no effect elsewhere.
AllowExpiredAt time.Duration
// AllowExpiredPaths enumerates the refresh-flow paths on which
// AllowExpiredAt applies.
AllowExpiredPaths []string
// RSAPublicKey is a static RSA public key for verifying RS256/384/512
// tokens without JWKS. Mutually usable with JWKSURL (JWKS takes precedence
// when a matching kid is found).
RSAPublicKey *rsa.PublicKey
// JWKSURL is an OIDC/JWKS endpoint. Keys are fetched, cached by "kid", and
// re-fetched on cache miss (rotation) or expiry.
JWKSURL string
// JWKSCacheTTL bounds how long JWKS entries are cached before a refresh.
// Defaults to defaultJWKSCacheTTL when zero.
JWKSCacheTTL time.Duration
// HTTPClient is used for JWKS fetches; defaults to a client with a short
// timeout when nil.
HTTPClient *http.Client
// AllowNoExpiry permits tokens that omit the "exp" claim. It defaults to
// false: a token with no expiry is rejected, so a signed token can never
// become a permanent, unrevocable credential. Set true only for trusted
// issuers that deliberately mint non-expiring service tokens.
AllowNoExpiry bool
// contains filtered or unexported fields
}
JWTConfig holds configuration for JWT authentication.
Backward compatibility: an HS256 deployment only needs Secret (and optionally Issuer/Audience/SkipPaths). RS256/OIDC deployments set either RSAPublicKey (a static key) or JWKSURL (dynamic keys fetched and cached by "kid", honoring rotation). The verification path is selected per-token from the JWS "alg" header, so both HS256 and RS256 tokens can be accepted by the same server if both a Secret and an RSA key source are configured.
type KeyRecord ¶ added in v0.5.0
type KeyRecord struct {
// ID is a stable, non-secret identifier/label for the key.
ID string `json:"id"`
// Hash is the hex-encoded SHA-256 of the raw key.
Hash string `json:"hash"`
// Scope is the RBAC scope/role granted to callers using this key.
Scope string `json:"scope"`
// UserID identifies the principal the key acts as.
UserID string `json:"user_id"`
// TenantID identifies the tenant for per-tenant quota accounting.
TenantID string `json:"tenant_id,omitempty"`
// Quota, when non-zero, is enforced per key on each request.
Quota Quota `json:"quota,omitempty"`
// Disabled revokes the key without deleting its record.
Disabled bool `json:"disabled,omitempty"`
// CreatedAt records when the key was added.
CreatedAt time.Time `json:"created_at"`
}
KeyRecord is the persisted metadata for an API key. The raw key is never stored; only its hash (see HashKey) is retained.
type KeyStore ¶ added in v0.5.0
type KeyStore interface {
// Lookup resolves the raw key to its record using constant-time comparison.
// It returns (record, true, nil) on match, (nil, false, nil) when no active
// key matches, and a non-nil error only on backend failure.
Lookup(ctx context.Context, rawKey string) (*KeyRecord, bool, error)
// Add hashes rawKey and stores the record; the returned record has Hash and
// CreatedAt populated. The raw key is not retained.
Add(ctx context.Context, rawKey string, meta KeyRecord) (KeyRecord, error)
// Remove deletes the record with the given ID.
Remove(ctx context.Context, id string) error
// List returns all stored records (without raw keys).
List(ctx context.Context) ([]KeyRecord, error)
}
KeyStore is a persisted, hashed API-key store. Implementations must never retain the raw key material and must compare using constant-time operations. The control plane can wire a database-backed implementation; MemoryKeyStore is the default.
type MemoryKeyStore ¶ added in v0.5.0
type MemoryKeyStore struct {
// contains filtered or unexported fields
}
MemoryKeyStore is an in-memory KeyStore keyed by record ID. It is safe for concurrent use.
func NewMemoryKeyStore ¶ added in v0.5.0
func NewMemoryKeyStore() *MemoryKeyStore
NewMemoryKeyStore returns an empty in-memory key store.
func (*MemoryKeyStore) Add ¶ added in v0.5.0
Add hashes and stores the key. If meta.ID is empty, the key hash is used as the ID.
func (*MemoryKeyStore) List ¶ added in v0.5.0
func (s *MemoryKeyStore) List(_ context.Context) ([]KeyRecord, error)
List returns a snapshot of all records.
type MemoryQuotaStore ¶ added in v0.5.0
type MemoryQuotaStore struct {
// contains filtered or unexported fields
}
MemoryQuotaStore is an in-memory QuotaStore with a rolling one-minute request window and a rolling 24h token/cost window. It is safe for concurrent use.
func NewMemoryQuotaStore ¶ added in v0.5.0
func NewMemoryQuotaStore() *MemoryQuotaStore
NewMemoryQuotaStore returns an empty in-memory quota store.
func (*MemoryQuotaStore) AddUsage ¶ added in v0.5.0
func (s *MemoryQuotaStore) AddUsage(_ context.Context, subject string, tokens int64, costUSD float64) error
AddUsage records token and cost consumption for the subject.
type Quota ¶ added in v0.5.0
type Quota struct {
// RequestsPerMinute caps requests within a rolling one-minute window.
// Enforced by APIKeyMiddleware.
RequestsPerMinute int `json:"requests_per_minute,omitempty"`
// TokensPerDay caps model tokens within a rolling 24h window. Enforced only
// once AddUsage is wired by the integrator (see the type doc).
TokensPerDay int64 `json:"tokens_per_day,omitempty"`
// MaxCostUSD caps cumulative spend (USD) within a rolling 24h window.
// Enforced only once AddUsage is wired by the integrator (see the type doc).
MaxCostUSD float64 `json:"max_cost_usd,omitempty"`
}
Quota defines rate and budget limits for a subject (an API key or a tenant). A zero value in any field means that dimension is unlimited.
Enforcement note: RequestsPerMinute is enforced out of the box by APIKeyMiddleware at request admission. TokensPerDay and MaxCostUSD are checked by Allow against recorded usage, but that usage is only populated when the integrator calls QuotaStore.AddUsage after a model call — the HTTP admission point has no visibility into token/cost, so these budgets do nothing until AddUsage is wired. For in-process token/cost budgeting that intercepts model usage directly, see engine/hooks/cost.go (CostTracker), which operates at the engine layer where usage actually exists.
type QuotaStore ¶ added in v0.5.0
type QuotaStore interface {
// Allow records one request against subject and reports whether it is within
// the given quota (rate + budget). A subject over any limit returns false.
Allow(ctx context.Context, subject string, q Quota) (bool, error)
// AddUsage records token and cost consumption for a subject.
AddUsage(ctx context.Context, subject string, tokens int64, costUSD float64) error
// Usage returns the current usage snapshot for a subject.
Usage(ctx context.Context, subject string) (Usage, error)
}
QuotaStore enforces and records per-subject usage. It is the extension point the control plane wires to attach token/cost budgets to requests:
- Allow is called at request admission time (enforces RequestsPerMinute, and TokensPerDay/MaxCostUSD against whatever usage has been recorded).
- AddUsage records token/cost spend and MUST be called by the integrator after model calls for the token/cost budgets to have any effect; it is not auto-wired because model usage is not available at HTTP admission. See the Quota doc and engine/hooks/cost.go for the in-engine alternative.
type Service ¶
type Service struct{}
Service manages authentication and authorization.
func NewService ¶
func NewService() *Service
func (*Service) CheckPermission ¶
func (s *Service) CheckPermission(claims *UserClaims, required Role) bool
CheckPermission verifies a user has the required role.
type Usage ¶ added in v0.5.0
type Usage struct {
Requests int `json:"requests"`
Tokens int64 `json:"tokens"`
CostUSD float64 `json:"cost_usd"`
}
Usage is a point-in-time snapshot of consumption for a subject.
type UserClaims ¶ added in v0.3.0
type UserClaims struct {
UserID string `json:"user_id,omitempty"`
Roles []string `json:"roles,omitempty"`
Exp int64 `json:"exp,omitempty"`
Issuer string `json:"iss,omitempty"`
Audience Audience `json:"aud,omitempty"`
Subject string `json:"sub,omitempty"`
TenantID string `json:"tenant_id,omitempty"`
}
UserClaims represents the decoded user information from a JWT.
func UserFromContext ¶ added in v0.3.0
func UserFromContext(ctx context.Context) (*UserClaims, bool)
UserFromContext extracts UserClaims from the request context.