Documentation
¶
Overview ¶
Package core provides the foundation types and interfaces for the OneAuth authentication framework. Every other OneAuth package imports core.
The core package contains:
- User, Identity, and Channel types (the data model)
- Store interfaces (UserStore, IdentityStore, ChannelStore, etc.)
- Token types and token store interface
- Credentials, signup policies, and validation types
- Scope constants and helpers
- Email sender interface
- Request context helpers (GetUserIDFromContext, SetUserIDInContext)
Index ¶
- Constants
- Variables
- func AllBuiltinScopes() []string
- func ContainsAllScopes(granted, required []string) bool
- func ContainsScope(scopes []string, scope string) bool
- func DetectUsernameType(username string) string
- func GenerateAPIKeyID() (string, error)
- func GenerateAPIKeySecret() (string, error)
- func GenerateSecureToken() (string, error)
- func GetUserIDFromContext(ctx context.Context) string
- func IdentityKey(identityType, identityValue string) string
- func IntersectScopes(requested, allowed []string) []string
- func JoinScopes(scopes []string) string
- func ParseScopes(scopeString string) []string
- func ScopesEqual(a, b []string) bool
- func SetUserIDInContext(ctx context.Context, userID string) context.Context
- func UnionScopes(a, b []string) []string
- func ValidateAll(details []AuthorizationDetail) error
- func ValidateRequestedScopes(requested, allowed []string) (valid, invalid []string)
- type APIKey
- type APIKeyStore
- type AccountLockout
- type AuthError
- type AuthErrorHandler
- type AuthToken
- type AuthorizationDetail
- type BasicUser
- type Channel
- type ChannelStore
- type ConsoleEmailSender
- type CreateUserFunc
- type Credentials
- type CredentialsValidator
- type GetUserScopesFunc
- type HandleUserFunc
- type Identity
- type IdentityStore
- type InMemoryBlacklist
- type InMemoryRateLimiter
- type RateLimiter
- type RefreshToken
- type RefreshTokenStore
- type SendEmail
- type SignupPolicy
- type SignupValidator
- type TokenBlacklist
- type TokenError
- type TokenPair
- type TokenRequest
- type TokenStore
- type TokenType
- type User
- type UserStore
- type UsernameStore
Constants ¶
const ( ErrCodeEmailExists = "email_exists" ErrCodeUsernameTaken = "username_taken" ErrCodeWeakPassword = "weak_password" ErrCodeInvalidUsername = "invalid_username" ErrCodeInvalidEmail = "invalid_email" ErrCodeInvalidPhone = "invalid_phone" ErrCodeMissingField = "missing_field" ErrCodeInvalidCreds = "invalid_credentials" )
Common error codes
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
const ( TokenExpiryEmailVerification = 24 * time.Hour // 24 hours TokenExpiryPasswordReset = 1 * time.Hour // 1 hour TokenExpiryAccessToken = 15 * time.Minute // 15 minutes TokenExpiryRefreshToken = 7 * 24 * time.Hour // 7 days )
Default token expiry durations
const DefaultUserParamName = "loggedInUserId"
DefaultUserParamName is the default context key for user ID
Variables ¶
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
var ErrInvalidAuthorizationDetails = fmt.Errorf("invalid_authorization_details")
ErrInvalidAuthorizationDetails is the OAuth error for malformed or disallowed authorization_details values (RFC 9396 §5.2).
var PolicyEmailOnly = SignupPolicy{ RequireUsername: false, RequireEmail: true, RequirePhone: false, RequirePassword: true, EnforceUsernameUnique: true, EnforceEmailUnique: true, MinPasswordLength: 8, UsernamePattern: `^[a-zA-Z0-9_-]{3,20}$`, }
PolicyEmailOnly requires only email and password for signup (username optional)
var PolicyFlexible = SignupPolicy{ RequireUsername: false, RequireEmail: false, RequirePhone: false, RequirePassword: false, EnforceUsernameUnique: true, EnforceEmailUnique: true, MinPasswordLength: 8, UsernamePattern: `^[a-zA-Z0-9_-]{3,20}$`, }
PolicyFlexible is OAuth-friendly - email/phone optional, username optional
var PolicyUsernameRequired = SignupPolicy{ RequireUsername: true, RequireEmail: true, RequirePhone: false, RequirePassword: true, EnforceUsernameUnique: true, EnforceEmailUnique: true, MinPasswordLength: 8, UsernamePattern: `^[a-zA-Z0-9_-]{3,20}$`, }
PolicyUsernameRequired requires username, email, and password for signup
Functions ¶
func AllBuiltinScopes ¶
func AllBuiltinScopes() []string
AllBuiltinScopes returns all built-in scope values
func ContainsAllScopes ¶
ContainsAllScopes checks if all required scopes are present in the granted scopes
func ContainsScope ¶
ContainsScope checks if a scope is present in the list
func DetectUsernameType ¶
DetectUsernameType attempts to detect what type of username was provided
func GenerateAPIKeyID ¶
GenerateAPIKeyID generates a new API key ID with prefix
func GenerateAPIKeySecret ¶
GenerateAPIKeySecret generates the secret portion of an API key
func GenerateSecureToken ¶
GenerateSecureToken generates a cryptographically secure random token
func GetUserIDFromContext ¶
GetUserIDFromContext retrieves the user ID from the request context Uses the default key "loggedInUserId"
func IdentityKey ¶
IdentityKey creates a consistent identity key from type and value
func IntersectScopes ¶
IntersectScopes returns the intersection of requested and allowed scopes The result contains only scopes that appear in both slices
func JoinScopes ¶
JoinScopes joins a slice of scopes into a space-separated string
func ParseScopes ¶
ParseScopes parses a space-separated scope string into a slice
func ScopesEqual ¶
ScopesEqual checks if two scope slices contain the same scopes (order-independent)
func SetUserIDInContext ¶
SetUserIDInContext sets the user ID in the request context
func UnionScopes ¶ added in v0.0.68
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 ¶
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
UserID string `json:"user_id"` // Owner of this key
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
type APIKeyStore ¶
type APIKeyStore interface {
// CreateAPIKey creates a new API key and returns the full key (only shown once)
// The key format is: keyID + "_" + secret
CreateAPIKey(userID, 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
// ListUserAPIKeys returns all API keys for a user (without secrets)
ListUserAPIKeys(userID 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 AuthError ¶
type AuthError struct {
Code string // "email_exists", "username_taken", "weak_password", "invalid_format", etc.
Message string // Human-readable message
Field string // Which form field has the error (e.g., "email", "username", "password")
}
AuthError represents a structured authentication error
func NewAuthError ¶
NewAuthError creates a new AuthError
type AuthErrorHandler ¶
AuthErrorHandler is called when authentication errors occur. The handler receives the structured error and should write the response. Returns true if the error was handled (response written), false to use default JSON response.
Example implementations:
- Redirect back to form with flash message (app uses their session library)
- Redirect with error in query params: /signup?error=email_exists
- Return JSON error response
- Log and show generic error page
type AuthToken ¶
type AuthToken struct {
Token string `json:"token"`
Type TokenType `json:"type"`
UserID string `json:"user_id"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at"`
}
AuthToken represents a verification or reset token
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.
type Channel ¶
type Channel struct {
Provider string `json:"provider"` // "local", "google", "github"
IdentityKey string `json:"identity_key"` // "email:john@example.com"
Credentials map[string]any `json:"credentials"` // password_hash, access_token, etc.
Profile map[string]any `json:"profile"` // optional data from provider
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ExpiresAt time.Time `json:"expires_at"` // when channel auth expires and needs re-auth
Version int `json:"version"` // optimistic locking version
}
Channel represents an authentication mechanism/provider
type ChannelStore ¶
type ChannelStore interface {
// GetChannel gets or optionally creates a channel
GetChannel(provider string, identityKey string, createIfMissing bool) (channel *Channel, newCreated bool, err error)
// SaveChannel creates or updates a channel (upsert)
SaveChannel(channel *Channel) error
// GetChannelsByIdentity returns all channels for an identity
GetChannelsByIdentity(identityKey string) ([]*Channel, error)
}
ChannelStore manages authentication channels/providers
type ConsoleEmailSender ¶
type ConsoleEmailSender struct{}
ConsoleEmailSender is a development implementation that logs emails to console
func (*ConsoleEmailSender) SendPasswordResetEmail ¶
func (c *ConsoleEmailSender) SendPasswordResetEmail(to string, resetLink string) error
func (*ConsoleEmailSender) SendVerificationEmail ¶
func (c *ConsoleEmailSender) SendVerificationEmail(to string, verificationLink string) error
type CreateUserFunc ¶
type CreateUserFunc func(creds *Credentials) (User, error)
CreateUserFunc creates a new user with the given credentials
type Credentials ¶
type Credentials struct {
Username string // Required for signup, can be username/email/phone for login
Email *string // Optional for signup
Phone *string // Optional for signup
Password string // Required
}
Credentials represents user credentials for signup or login
type CredentialsValidator ¶
CredentialsValidator validates credentials during login and returns the user
type GetUserScopesFunc ¶
GetUserScopesFunc is a callback that returns allowed scopes for a user. Applications implement this to determine what scopes a user is allowed to have. This can be based on user roles, profile data, groups, etc.
func DefaultGetUserScopes ¶
func DefaultGetUserScopes() GetUserScopesFunc
DefaultGetUserScopes returns a default implementation that grants basic scopes to all users
type HandleUserFunc ¶
type HandleUserFunc func(authtype string, provider string, token *oauth2.Token, userInfo map[string]any, w http.ResponseWriter, r *http.Request)
HandleUserFunc is called after successful authentication (OAuth or local).
type Identity ¶
type Identity struct {
Type string `json:"type"` // "email", "phone"
Value string `json:"value"` // "john@example.com", "+1-555-1234"
UserID string `json:"user_id"` // which user owns this identity
Verified bool `json:"verified"` // has any channel verified this identity?
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Version int `json:"version"` // optimistic locking version
}
Identity represents a contact method (email, phone) that can be verified
type IdentityStore ¶
type IdentityStore interface {
// GetIdentity gets or optionally creates an identity
GetIdentity(identityType, identityValue string, createIfMissing bool) (identity *Identity, newCreated bool, err error)
// SaveIdentity creates or updates an identity (upsert)
SaveIdentity(identity *Identity) error
// SetUserForIdentity associates an identity with a user
SetUserForIdentity(identityType, identityValue string, newUserId string) error
// MarkIdentityVerified marks an identity as verified
MarkIdentityVerified(identityType, identityValue string) error
// GetUserIdentities returns all identities for a user
GetUserIdentities(userId string) ([]*Identity, error)
}
IdentityStore manages contact identities (email, phone)
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).
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)
UserID string `json:"user_id"` // Associated user
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 a user
CreateRefreshToken(userID, 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
// RevokeUserTokens revokes all refresh tokens for a user
RevokeUserTokens(userID string) error
// RevokeTokenFamily revokes all tokens in a family (theft detection)
RevokeTokenFamily(family string) error
// GetUserTokens lists all active (non-revoked, non-expired) refresh tokens for a user
GetUserTokens(userID string) ([]*RefreshToken, error)
// CleanupExpiredTokens removes expired tokens (for maintenance)
CleanupExpiredTokens() error
}
RefreshTokenStore manages refresh tokens for API access
type SendEmail ¶
type SendEmail interface {
SendVerificationEmail(to string, verificationLink string) error
SendPasswordResetEmail(to string, resetLink string) error
}
SendEmail interface allows applications to provide their own email sending implementation
type SignupPolicy ¶
type SignupPolicy struct {
RequireUsername bool // Is username required? (default: false)
RequireEmail bool // Is email required? (default: true)
RequirePhone bool // Is phone required? (default: false)
RequirePassword bool // Is password required for local? (default: true)
EnforceUsernameUnique bool // Check UsernameStore? (default: true if username required)
EnforceEmailUnique bool // Check IdentityStore? (default: true)
MinPasswordLength int // Minimum password (default: 8)
UsernamePattern string // Regex for username (default: ^[a-zA-Z0-9_-]{3,20}$)
}
SignupPolicy defines what is required for signup
func DefaultSignupPolicy ¶
func DefaultSignupPolicy() SignupPolicy
DefaultSignupPolicy returns a sensible default signup policy
func (SignupPolicy) GetMinPasswordLength ¶
func (p SignupPolicy) GetMinPasswordLength() int
GetMinPasswordLength returns the minimum password length
func (SignupPolicy) GetUsernamePattern ¶
func (p SignupPolicy) GetUsernamePattern() *regexp.Regexp
GetUsernamePattern returns the compiled username regex pattern
type SignupValidator ¶
type SignupValidator func(creds *Credentials) error
SignupValidator validates credentials during signup
var DefaultSignupValidator SignupValidator = func(creds *Credentials) error { if len(creds.Username) < 3 || len(creds.Username) > 20 { return fmt.Errorf("username must be 3-20 characters") } usernameRegex := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`) if !usernameRegex.MatchString(creds.Username) { return fmt.Errorf("username can only contain letters, numbers, underscores, and hyphens") } if creds.Email == nil && creds.Phone == nil { return fmt.Errorf("email or phone required") } if creds.Email != nil && *creds.Email != "" { emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) if !emailRegex.MatchString(*creds.Email) { return fmt.Errorf("invalid email format") } } if creds.Phone != nil && *creds.Phone != "" { cleaned := strings.ReplaceAll(*creds.Phone, "-", "") cleaned = strings.ReplaceAll(cleaned, " ", "") cleaned = strings.ReplaceAll(cleaned, "(", "") cleaned = strings.ReplaceAll(cleaned, ")", "") if len(cleaned) < 10 { return fmt.Errorf("invalid phone number") } } if len(creds.Password) < 8 { return fmt.Errorf("password must be at least 8 characters") } return nil }
DefaultSignupValidator provides sensible default validation for signup
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
type TokenStore ¶
type TokenStore interface {
CreateToken(userID, email string, tokenType TokenType, expiryDuration time.Duration) (*AuthToken, error)
GetToken(token string) (*AuthToken, error)
DeleteToken(token string) error
DeleteUserTokens(userID string, tokenType TokenType) error
}
TokenStore interface for managing auth tokens
type UserStore ¶
type UserStore interface {
// CreateUser creates a new user with the given ID and profile
CreateUser(userId string, isActive bool, profile map[string]any) (User, error)
// GetUserById retrieves a user by their ID
GetUserById(userId string) (User, error)
// SaveUser creates or updates a user (upsert)
SaveUser(user User) error
}
UserStore manages unified user accounts
type UsernameStore ¶
type UsernameStore interface {
// ReserveUsername reserves a username for a user (creates username -> userID mapping)
// Returns error if username is already taken
ReserveUsername(username string, userID string) error
// GetUserByUsername looks up a userID by username
// Returns error if username not found
GetUserByUsername(username string) (userID string, err error)
// ReleaseUsername removes a username reservation
ReleaseUsername(username string) error
// ChangeUsername atomically changes a username (release old, reserve new)
// Returns error if new username is already taken
ChangeUsername(oldUsername, newUsername, userID string) error
}
UsernameStore manages username uniqueness (optional - for apps that need username-based login)