core

package
v0.1.5 Latest Latest
Warning

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

Go to latest
Published: May 26, 2026 License: Apache-2.0 Imports: 9 Imported by: 2

Documentation

Index

Constants

View Source
const (
	ScopeRead    = "read"    // Read access to user data
	ScopeWrite   = "write"   // Write access to user data
	ScopeProfile = "profile" // Access to user profile information
	ScopeOffline = "offline" // Enable refresh tokens (long-lived sessions)
	ScopeAdmin   = "admin"   // Administrative access
)

Built-in scope constants

View Source
const (
	TokenExpiryAccessToken  = 15 * time.Minute   // 15 minutes
	TokenExpiryRefreshToken = 7 * 24 * time.Hour // 7 days
)

Default token expiry durations for OAuth tokens.

View Source
const DefaultSubjectParamName = "loggedInSubject"

DefaultSubjectParamName is the default context / session key for the authenticated principal — RFC 7519 `sub`. Holds a user ID for human-driven flows and a client_id for client_credentials.

Variables

View Source
var (
	ErrTokenNotFound  = fmt.Errorf("token not found")
	ErrTokenExpired   = fmt.Errorf("token expired")
	ErrTokenRevoked   = fmt.Errorf("token revoked")
	ErrTokenReused    = fmt.Errorf("token reuse detected")
	ErrInvalidGrant   = fmt.Errorf("invalid grant")
	ErrInvalidScope   = fmt.Errorf("invalid scope")
	ErrAPIKeyNotFound = fmt.Errorf("api key not found")
)

Common errors for token operations.

View Source
var ErrInvalidAuthorizationDetails = fmt.Errorf("invalid_authorization_details")

ErrInvalidAuthorizationDetails is the OAuth error for malformed or disallowed authorization_details values (RFC 9396 §5.2).

Functions

func AllBuiltinScopes

func AllBuiltinScopes() []string

AllBuiltinScopes returns all built-in scope values

func ContainsAllScopes

func ContainsAllScopes(granted, required []string) bool

ContainsAllScopes checks if all required scopes are present in the granted scopes

func ContainsScope

func ContainsScope(scopes []string, scope string) bool

ContainsScope checks if a scope is present in the list

func GenerateAPIKeyID

func GenerateAPIKeyID() (string, error)

GenerateAPIKeyID generates a new API key ID with prefix.

func GenerateAPIKeySecret

func GenerateAPIKeySecret() (string, error)

GenerateAPIKeySecret generates the secret portion of an API key.

func GenerateSecureToken

func GenerateSecureToken() (string, error)

GenerateSecureToken generates a cryptographically secure random token.

func GetSubjectFromContext added in v0.1.4

func GetSubjectFromContext(ctx context.Context) string

GetSubjectFromContext retrieves the authenticated subject from the request context. Uses the default key DefaultSubjectParamName.

func IntersectScopes

func IntersectScopes(requested, allowed []string) []string

IntersectScopes returns the intersection of requested and allowed scopes The result contains only scopes that appear in both slices

func JoinScopes

func JoinScopes(scopes []string) string

JoinScopes joins a slice of scopes into a space-separated string

func ParseScopes

func ParseScopes(scopeString string) []string

ParseScopes parses a space-separated scope string into a slice

func ScopesEqual

func ScopesEqual(a, b []string) bool

ScopesEqual checks if two scope slices contain the same scopes (order-independent)

func SetSubjectInContext added in v0.1.4

func SetSubjectInContext(ctx context.Context, subject string) context.Context

SetSubjectInContext sets the authenticated subject in the request context.

func UnionScopes added in v0.0.68

func UnionScopes(a, b []string) []string

UnionScopes returns the union of two scope slices, deduplicated and sorted. This is the complement of IntersectScopes — use UnionScopes to merge scope sets (e.g., combining existing and newly requested scopes), and IntersectScopes to restrict scope sets (e.g., filtering requested scopes against allowed scopes).

func ValidateAll added in v0.0.76

func ValidateAll(details []AuthorizationDetail) error

ValidateAll validates a slice of AuthorizationDetail values. Returns nil if the slice is nil or empty.

func ValidateRequestedScopes

func ValidateRequestedScopes(requested, allowed []string) (valid, invalid []string)

ValidateRequestedScopes validates that all requested scopes are from the allowed set Returns the valid scopes and any invalid scopes found

Types

type APIKey

type APIKey struct {
	KeyID      string     `json:"key_id"`   // Public identifier (e.g., "oa_abc123...")
	KeyHash    string     `json:"key_hash"` // bcrypt hash of the secret portion
	Subject    string     `json:"subject"`  // Principal the key represents (RFC 7519 sub) — user ID for user-bound keys, client_id for service accounts
	Name       string     `json:"name"`     // User-defined label
	Scopes     []string   `json:"scopes"`   // Allowed scopes
	CreatedAt  time.Time  `json:"created_at"`
	ExpiresAt  *time.Time `json:"expires_at,omitempty"` // Optional expiry
	LastUsedAt time.Time  `json:"last_used_at"`
	RevokedAt  *time.Time `json:"revoked_at,omitempty"`
	Revoked    bool       `json:"revoked"`
}

APIKey represents a long-lived API key for programmatic access.

func (*APIKey) IsExpired

func (k *APIKey) IsExpired() bool

IsExpired checks if an API key has expired.

func (*APIKey) IsValid

func (k *APIKey) IsValid() bool

IsValid checks if an API key is valid (not expired and not revoked).

type APIKeyStore

type APIKeyStore interface {
	// CreateAPIKey creates a new API key for the given subject and returns
	// the full key (only shown once). The key format is keyID + "_" + secret.
	CreateAPIKey(subject, name string, scopes []string, expiresAt *time.Time) (fullKey string, apiKey *APIKey, err error)

	// GetAPIKeyByID retrieves an API key by its public ID.
	GetAPIKeyByID(keyID string) (*APIKey, error)

	// ValidateAPIKey validates a full API key and returns the key metadata if valid.
	// The fullKey format is: keyID + "_" + secret.
	ValidateAPIKey(fullKey string) (*APIKey, error)

	// RevokeAPIKey marks an API key as revoked.
	RevokeAPIKey(keyID string) error

	// ListSubjectAPIKeys returns all API keys owned by a subject (without secrets).
	ListSubjectAPIKeys(subject string) ([]*APIKey, error)

	// UpdateAPIKeyLastUsed updates the last used timestamp.
	UpdateAPIKeyLastUsed(keyID string) error
}

APIKeyStore manages API keys for programmatic access.

type AccountLockout added in v0.0.44

type AccountLockout struct {
	MaxAttempts  int           // consecutive failures before lockout (default: 5)
	LockDuration time.Duration // how long the lockout lasts (default: 15 min)
	// contains filtered or unexported fields
}

AccountLockout tracks consecutive authentication failures per key and locks accounts after a configurable number of attempts. Lockouts expire automatically after LockDuration. Thread-safe.

func NewAccountLockout added in v0.0.44

func NewAccountLockout(maxAttempts int, lockDuration time.Duration) *AccountLockout

NewAccountLockout creates an AccountLockout with the given thresholds.

func (*AccountLockout) IsLocked added in v0.0.44

func (l *AccountLockout) IsLocked(key string) bool

IsLocked returns true if the key is currently locked out.

func (*AccountLockout) RecordFailure added in v0.0.44

func (l *AccountLockout) RecordFailure(key string) bool

RecordFailure records a failed authentication attempt. Returns true if the account is now locked (threshold reached).

func (*AccountLockout) RecordSuccess added in v0.0.44

func (l *AccountLockout) RecordSuccess(key string)

RecordSuccess resets the failure counter for a key (successful login).

func (*AccountLockout) Reset added in v0.0.44

func (l *AccountLockout) Reset(key string)

Reset unlocks an account (admin action).

type AuthorizationDetail added in v0.0.76

type AuthorizationDetail struct {
	// Type is the authorization details type identifier (required).
	Type string `json:"type"`

	// Locations is an array of URIs indicating where the requested
	// resource can be found.
	Locations []string `json:"locations,omitempty"`

	// Actions is an array of strings describing the operations to be
	// performed on the resource.
	Actions []string `json:"actions,omitempty"`

	// DataTypes is an array of strings describing the types of data
	// being requested.
	DataTypes []string `json:"datatypes,omitempty"`

	// Identifier is a string identifying a specific resource at the API.
	Identifier string `json:"identifier,omitempty"`

	// Privileges is an array of strings representing privilege levels.
	Privileges []string `json:"privileges,omitempty"`

	// Extra holds API-specific extension fields. These are flattened into
	// the top-level JSON object (not nested under an "extra" key).
	Extra map[string]any `json:"-"`
}

AuthorizationDetail represents a single authorization details object per RFC 9396 §2. Each object describes fine-grained authorization requirements for a specific API or resource type.

The Type field is required and identifies the authorization details type. Common fields (Locations, Actions, DataTypes, Identifier, Privileges) are optional and defined by the spec. API-specific extension fields are stored in Extra and flattened into the top-level JSON object on marshal.

See: https://www.rfc-editor.org/rfc/rfc9396#section-2

func FilterByType added in v0.0.76

func FilterByType(details []AuthorizationDetail, typ string) []AuthorizationDetail

FilterByType returns the subset of details matching the given type. Returns nil if no matches are found.

func (AuthorizationDetail) MarshalJSON added in v0.0.76

func (ad AuthorizationDetail) MarshalJSON() ([]byte, error)

MarshalJSON flattens Extra fields into the top-level JSON object alongside the common RFC 9396 fields. This produces the flat structure required by the spec (e.g., {"type":"payment","amount":"45"}) rather than nesting extensions.

func (*AuthorizationDetail) UnmarshalJSON added in v0.0.76

func (ad *AuthorizationDetail) UnmarshalJSON(data []byte) error

UnmarshalJSON parses a flat JSON object into the common fields and Extra. Extension keys that collide with common field names are rejected.

func (*AuthorizationDetail) Validate added in v0.0.76

func (ad *AuthorizationDetail) Validate() error

Validate checks that the AuthorizationDetail has a non-empty Type field.

See: https://www.rfc-editor.org/rfc/rfc9396#section-2

type GetSubjectScopesFunc added in v0.1.4

type GetSubjectScopesFunc func(subject string) ([]string, error)

GetSubjectScopesFunc is a callback that returns allowed scopes for a principal. Applications implement this to determine what scopes a subject (user ID for human-driven flows, client_id for client_credentials) is allowed to have.

func DefaultGetSubjectScopes added in v0.1.4

func DefaultGetSubjectScopes() GetSubjectScopesFunc

DefaultGetSubjectScopes returns a default implementation that grants basic scopes to all subjects.

type InMemoryBlacklist added in v0.0.48

type InMemoryBlacklist struct {
	// contains filtered or unexported fields
}

InMemoryBlacklist is a thread-safe in-memory TokenBlacklist. Suitable for single-process deployments. For distributed deployments, use a Redis-backed implementation with the same interface.

func NewInMemoryBlacklist added in v0.0.48

func NewInMemoryBlacklist() *InMemoryBlacklist

NewInMemoryBlacklist creates a new in-memory blacklist.

func (*InMemoryBlacklist) CleanupExpired added in v0.0.48

func (b *InMemoryBlacklist) CleanupExpired()

CleanupExpired removes entries whose tokens have naturally expired. Call periodically (e.g., every minute) to prevent memory growth.

func (*InMemoryBlacklist) IsRevoked added in v0.0.48

func (b *InMemoryBlacklist) IsRevoked(jti string) bool

IsRevoked returns true if the token ID is in the blacklist and hasn't expired.

func (*InMemoryBlacklist) Len added in v0.0.48

func (b *InMemoryBlacklist) Len() int

Len returns the number of entries (including expired ones not yet cleaned).

func (*InMemoryBlacklist) Revoke added in v0.0.48

func (b *InMemoryBlacklist) Revoke(jti string, expiry time.Time) error

Revoke adds a token ID to the blacklist until its expiry time.

type InMemoryRateLimiter added in v0.0.44

type InMemoryRateLimiter struct {
	// contains filtered or unexported fields
}

InMemoryRateLimiter implements RateLimiter using a token bucket algorithm. Each key gets an independent bucket that refills at a steady rate. Thread-safe for concurrent use.

func NewInMemoryRateLimiter added in v0.0.44

func NewInMemoryRateLimiter(rate float64, burst int) *InMemoryRateLimiter

NewInMemoryRateLimiter creates a rate limiter that allows `rate` requests per second with a burst capacity of `burst`.

Example: NewInMemoryRateLimiter(0.5, 5) allows 1 request per 2 seconds sustained, with bursts of up to 5 requests.

func (*InMemoryRateLimiter) Allow added in v0.0.44

func (r *InMemoryRateLimiter) Allow(key string) bool

Allow returns true if the key has tokens remaining.

func (*InMemoryRateLimiter) CleanupStale added in v0.0.44

func (r *InMemoryRateLimiter) CleanupStale(maxAge time.Duration)

CleanupStale removes buckets that haven't been used for the given duration. Call periodically to prevent memory growth from abandoned keys.

type RateLimiter added in v0.0.44

type RateLimiter interface {
	// Allow returns true if the operation is permitted for the given key.
	// Returns false if the rate limit has been exceeded.
	Allow(key string) bool
}

RateLimiter controls the rate of operations (e.g., login attempts) per key. Keys are typically IP addresses, usernames, or a combination.

type RefreshToken

type RefreshToken struct {
	Token                string                `json:"token"`                           // 64-char hex token value
	TokenHash            string                `json:"token_hash"`                      // SHA256 hash for storage (optional)
	Subject              string                `json:"subject"`                         // Principal the token represents (RFC 7519 sub) — user ID for user-bound tokens, client_id for client_credentials
	ClientID             string                `json:"client_id"`                       // Optional client/app identifier
	DeviceInfo           map[string]any        `json:"device_info"`                     // User agent, IP, etc.
	Family               string                `json:"family"`                          // Token family for rotation tracking
	Generation           int                   `json:"generation"`                      // Increments on rotation
	Scopes               []string              `json:"scopes"`                          // Granted scopes
	AuthorizationDetails []AuthorizationDetail `json:"authorization_details,omitempty"` // RFC 9396
	CreatedAt            time.Time             `json:"created_at"`
	ExpiresAt            time.Time             `json:"expires_at"`
	LastUsedAt           time.Time             `json:"last_used_at"`
	RevokedAt            *time.Time            `json:"revoked_at,omitempty"`
	Revoked              bool                  `json:"revoked"`
}

RefreshToken represents a long-lived refresh token for API access.

func (*RefreshToken) IsExpired

func (t *RefreshToken) IsExpired() bool

IsExpired checks if a refresh token has expired.

func (*RefreshToken) IsValid

func (t *RefreshToken) IsValid() bool

IsValid checks if a refresh token is valid (not expired and not revoked).

type RefreshTokenStore

type RefreshTokenStore interface {
	// CreateRefreshToken creates a new refresh token for the given subject
	// (RFC 7519 sub) — a user ID for user-bound tokens, a client_id for
	// client_credentials.
	CreateRefreshToken(subject, clientID string, deviceInfo map[string]any, scopes []string) (*RefreshToken, error)

	// GetRefreshToken retrieves a refresh token by its value.
	GetRefreshToken(token string) (*RefreshToken, error)

	// RotateRefreshToken invalidates old token and creates new one in same family.
	// Returns ErrTokenReused if the old token was already revoked (theft detection).
	RotateRefreshToken(oldToken string) (*RefreshToken, error)

	// RevokeRefreshToken marks a token as revoked.
	RevokeRefreshToken(token string) error

	// RevokeSubjectTokens revokes all refresh tokens belonging to a subject.
	RevokeSubjectTokens(subject string) error

	// RevokeTokenFamily revokes all tokens in a family (theft detection).
	RevokeTokenFamily(family string) error

	// GetSubjectTokens lists all active (non-revoked, non-expired) refresh
	// tokens for a subject.
	GetSubjectTokens(subject string) ([]*RefreshToken, error)

	// CleanupExpiredTokens removes expired tokens (for maintenance).
	CleanupExpiredTokens() error
}

RefreshTokenStore manages refresh tokens for API access.

type TokenBlacklist added in v0.0.48

type TokenBlacklist interface {
	// Revoke adds a token ID to the blacklist. The entry should be kept
	// until expiry (when the token would naturally expire anyway).
	Revoke(jti string, expiry time.Time) error

	// IsRevoked returns true if the token ID has been revoked and hasn't expired.
	IsRevoked(jti string) bool
}

TokenBlacklist tracks revoked JWT access tokens by their jti (JWT ID) claim. Entries auto-expire when the original token would have expired, preventing unbounded growth. Pluggable: in-memory for single-node, Redis for distributed.

See: https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7

type TokenError

type TokenError struct {
	Error            string `json:"error"`
	ErrorDescription string `json:"error_description,omitempty"`
}

TokenError represents an OAuth 2.0 compliant error response.

type TokenPair

type TokenPair struct {
	AccessToken          string                `json:"access_token"`
	TokenType            string                `json:"token_type"` // "Bearer"
	ExpiresIn            int64                 `json:"expires_in"` // Seconds until access token expires
	RefreshToken         string                `json:"refresh_token,omitempty"`
	Scope                string                `json:"scope,omitempty"`
	AuthorizationDetails []AuthorizationDetail `json:"authorization_details,omitempty"` // RFC 9396

	// IssuedTokenType identifies the type of token issued (RFC 8693 §2.2,
	// REQUIRED for token-exchange responses; absent for other grants).
	// Common values:
	//   urn:ietf:params:oauth:token-type:access_token
	//   urn:ietf:params:oauth:token-type:refresh_token
	//   urn:ietf:params:oauth:token-type:jwt
	IssuedTokenType string `json:"issued_token_type,omitempty"`
}

TokenPair represents the response from a successful authentication.

type TokenRequest

type TokenRequest struct {
	GrantType            string                `json:"grant_type"`                      // "password", "refresh_token", "client_credentials", "urn:ietf:params:oauth:grant-type:jwt-bearer", "urn:ietf:params:oauth:grant-type:token-exchange"
	Username             string                `json:"username,omitempty"`              // For password grant
	Password             string                `json:"password,omitempty"`              // For password grant
	RefreshToken         string                `json:"refresh_token,omitempty"`         // For refresh_token grant
	Scope                string                `json:"scope,omitempty"`                 // Requested scopes
	ClientID             string                `json:"client_id,omitempty"`             // Client identifier (for client_credentials, optional for others)
	ClientSecret         string                `json:"client_secret,omitempty"`         // Client secret (for client_credentials via client_secret_post)
	AuthorizationDetails []AuthorizationDetail `json:"authorization_details,omitempty"` // RFC 9396

	// Assertion is the signed JWT for grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
	// (RFC 7523 §2.1). The AS validates the assertion against a configured
	// trusted-issuer registry and issues an access token in exchange.
	Assertion string `json:"assertion,omitempty"`

	// ClientAssertionType + ClientAssertion authenticate the *client*
	// itself — distinct from Assertion (which authenticates the
	// resource owner via the jwt-bearer grant). Per RFC 7521 §4.2 +
	// RFC 7523 §2.2 + OIDC Core §9, ClientAssertionType MUST be
	// "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" and
	// ClientAssertion is a signed JWT with iss == sub == client_id.
	// Used by the private_key_jwt and client_secret_jwt token-endpoint
	// authentication methods.
	ClientAssertionType string `json:"client_assertion_type,omitempty"`
	ClientAssertion     string `json:"client_assertion,omitempty"`

	// SubjectToken / SubjectTokenType / RequestedTokenType / Resource / Audience
	// support grant_type=urn:ietf:params:oauth:grant-type:token-exchange (RFC 8693).
	// SubjectToken is the credential representing the party on whose behalf
	// the request is made; SubjectTokenType identifies its kind (e.g.,
	// urn:ietf:params:oauth:token-type:jwt). RequestedTokenType (optional)
	// identifies the desired output token kind; defaults to access_token.
	SubjectToken       string `json:"subject_token,omitempty"`
	SubjectTokenType   string `json:"subject_token_type,omitempty"`
	RequestedTokenType string `json:"requested_token_type,omitempty"`
	Resource           string `json:"resource,omitempty"`
	Audience           string `json:"audience,omitempty"`
}

TokenRequest represents a request to the token endpoint.

Jump to

Keyboard shortcuts

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