Documentation
¶
Overview ¶
Package memory provides an in-memory implementation of the OAuth storage interfaces.
This package implements TokenStore, ClientStore, and FlowStore interfaces using Go's built-in maps with mutex protection for thread safety. It is suitable for development, testing, and single-instance deployments where persistence is not required.
Features:
- Thread-safe operations using sync.RWMutex
- Automatic cleanup of expired tokens, codes, and flows
- Configurable cleanup intervals
- Token encryption support via Encryptor
- Audit logging support via Auditor
For production deployments requiring persistence or multi-instance deployments, use the storage/valkey package instead.
Example usage:
// Plain store (no encryption, default 1m cleanup interval): store := memory.New() defer store.Stop() // With encryption at rest and a custom cleanup interval: key, _ := security.GenerateKey() enc, _ := security.NewEncryptor(key) store := memory.New(memory.WithEncryptor(enc), memory.WithCleanupInterval(30*time.Second)) defer store.Stop() // Use store for TokenStore, ClientStore, and FlowStore interfaces: server, _ := oauth.NewServer(provider, store, store, store, config, logger)
Package memory provides an in-memory implementation of all storage interfaces. It is suitable for development, testing, and single-instance deployments.
Index ¶
- type Option
- type RefreshTokenFamily
- type Store
- func (s *Store) AtomicCheckAndMarkAuthCodeUsed(_ context.Context, code string) (*storage.AuthorizationCode, error)
- func (s *Store) AtomicGetAndDeleteRefreshToken(_ context.Context, refreshToken string) (string, string, *oauth2.Token, error)
- func (s *Store) CheckIPLimit(_ context.Context, ip string, maxClientsPerIP int) error
- func (s *Store) DeleteAuthorizationCode(_ context.Context, code string) error
- func (s *Store) DeleteAuthorizationState(_ context.Context, stateID string) error
- func (s *Store) DeleteClient(_ context.Context, clientID string) error
- func (s *Store) DeleteRefreshToken(_ context.Context, refreshToken string) error
- func (s *Store) DeleteToken(ctx context.Context, userID string) error
- func (s *Store) GetActiveRefreshTokenByFamily(_ context.Context, familyID string) (refreshToken, clientID string, err error)
- func (s *Store) GetAuthorizationCode(_ context.Context, code string) (*storage.AuthorizationCode, error)
- func (s *Store) GetAuthorizationState(_ context.Context, stateID string) (*storage.AuthorizationState, error)
- func (s *Store) GetAuthorizationStateByProviderState(_ context.Context, providerState string) (*storage.AuthorizationState, error)
- func (s *Store) GetClient(ctx context.Context, clientID string) (*storage.Client, error)
- func (s *Store) GetRefreshTokenFamily(_ context.Context, refreshToken string) (*storage.RefreshTokenFamilyMetadata, error)
- func (s *Store) GetRefreshTokenFamilyByID(_ context.Context, familyID string) (*storage.RefreshTokenFamilyMetadata, error)
- func (s *Store) GetRefreshTokenInfo(_ context.Context, refreshToken string) (string, error)
- func (s *Store) GetToken(ctx context.Context, userID string) (*oauth2.Token, error)
- func (s *Store) GetTokenMetadata(tokenID string) (*storage.TokenMetadata, error)
- func (s *Store) GetTokensByUserClient(_ context.Context, userID, clientID string) ([]string, error)
- func (s *Store) GetUserInfo(ctx context.Context, userID string) (*storage.UserInfo, error)
- func (s *Store) IsJTIRevoked(_ context.Context, jti string) (bool, error)
- func (s *Store) ListClients(_ context.Context) ([]*storage.Client, error)
- func (s *Store) LogValue() slog.Value
- func (s *Store) RevokeAllTokensForUserClient(_ context.Context, userID, clientID string) (int, error)
- func (s *Store) RevokeJTI(_ context.Context, jti string, expiresAt time.Time) error
- func (s *Store) RevokeRefreshTokenFamily(_ context.Context, familyID string) error
- func (s *Store) SaveAuthorizationCode(ctx context.Context, code *storage.AuthorizationCode) error
- func (s *Store) SaveAuthorizationState(_ context.Context, state *storage.AuthorizationState) error
- func (s *Store) SaveClient(ctx context.Context, client *storage.Client) error
- func (s *Store) SaveRefreshToken(_ context.Context, refreshToken, userID string, expiresAt time.Time) error
- func (s *Store) SaveRefreshTokenWithFamily(_ context.Context, refreshToken, userID, clientID, familyID string, ...) error
- func (s *Store) SaveToken(ctx context.Context, userID string, token *oauth2.Token) error
- func (s *Store) SaveTokenMetadata(_ context.Context, tokenID string, metadata storage.TokenMetadata) error
- func (s *Store) SaveUserInfo(ctx context.Context, userID string, info *storage.UserInfo) error
- func (s *Store) Stop()
- func (s *Store) TrackClientIP(_ context.Context, _ string, ip string) error
- func (s *Store) ValidateClientSecret(ctx context.Context, clientID, clientSecret string) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Option ¶ added in v0.2.160
type Option func(*Store)
Option configures a Store at construction time.
func WithCleanupInterval ¶ added in v0.2.160
WithCleanupInterval sets the background cleanup tick interval. Values ≤ 0 are clamped to 1 minute.
func WithEncryptor ¶ added in v0.2.160
WithEncryptor enables AES-256-GCM encryption at rest for all stored tokens. Encryption is optional; omit to store tokens in plaintext.
func WithInstrumentation ¶ added in v0.2.160
func WithInstrumentation(inst *instrumentation.Instrumentation) Option
WithInstrumentation wires OpenTelemetry tracing and metrics into the store.
func WithLogger ¶ added in v0.2.160
WithLogger replaces the default slog.Default() logger.
func WithRevokedFamilyRetentionDays ¶ added in v0.2.160
WithRevokedFamilyRetentionDays sets how long revoked token family metadata is kept for security forensics. Default is 90 days.
type RefreshTokenFamily ¶
type RefreshTokenFamily struct {
FamilyID string // Unique identifier for this token family
UserID string // User who owns this family
ClientID string // Client who owns this family
Generation int // Increments with each rotation
IssuedAt time.Time // When this generation was issued
Revoked bool // True if family has been revoked due to reuse detection
RevokedAt time.Time // When this family was revoked (for cleanup purposes)
}
RefreshTokenFamily tracks a family of refresh tokens for reuse detection (OAuth 2.1)
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is an in-memory implementation of all storage interfaces. It implements TokenStore, ClientStore, FlowStore, RefreshTokenFamilyStore, and TokenRevocationStore.
func New ¶
New creates a new in-memory store. All dependencies (encryptor, instrumentation, logger) are supplied at construction via options and are immutable afterward.
func (*Store) AtomicCheckAndMarkAuthCodeUsed ¶ added in v0.1.11
func (s *Store) AtomicCheckAndMarkAuthCodeUsed(_ context.Context, code string) (*storage.AuthorizationCode, error)
AtomicCheckAndMarkAuthCodeUsed atomically checks if a code is unused and marks it as used. This prevents race conditions in authorization code reuse detection. Returns the auth code if successful, or an error if code is already used.
SECURITY: This operation is atomic - only ONE concurrent request can succeed. All other concurrent requests will receive an "already used" error.
IMPORTANT: The authCode is ONLY returned on reuse errors (Used=true) to enable detection and revocation. For other errors (not found, expired), nil is returned to prevent information leakage.
func (*Store) AtomicGetAndDeleteRefreshToken ¶ added in v0.1.11
func (s *Store) AtomicGetAndDeleteRefreshToken(_ context.Context, refreshToken string) (string, string, *oauth2.Token, error)
AtomicGetAndDeleteRefreshToken atomically retrieves and deletes a refresh token. This prevents race conditions in refresh token rotation and reuse detection. Returns the userID, clientID, and provider token if successful.
SECURITY: This operation is atomic - only ONE concurrent request can succeed. All other concurrent requests will receive a "token not found" error. SECURITY: Returns clientID for client binding validation per OAuth 2.1 Section 6.
func (*Store) CheckIPLimit ¶
CheckIPLimit checks if an IP has reached the client registration limit
func (*Store) DeleteAuthorizationCode ¶
DeleteAuthorizationCode removes an authorization code
func (*Store) DeleteAuthorizationState ¶
DeleteAuthorizationState removes an authorization state Removes both client state and provider state entries
func (*Store) DeleteClient ¶ added in v0.2.145
DeleteClient removes a registered client by ID.
func (*Store) DeleteRefreshToken ¶
DeleteRefreshToken removes a refresh token (used for rotation)
func (*Store) DeleteToken ¶
DeleteToken removes a token for a user
func (*Store) GetActiveRefreshTokenByFamily ¶ added in v0.2.122
func (s *Store) GetActiveRefreshTokenByFamily(_ context.Context, familyID string) (refreshToken, clientID string, err error)
GetActiveRefreshTokenByFamily returns the most recent (highest generation) non-revoked refresh token for the family along with the owning client ID (storage.ActiveRefreshTokenByFamilyStore). Linear scan, same shape as GetRefreshTokenFamilyByID.
Returns ErrRefreshTokenFamilyNotFound when no entry matches at all, or ErrRefreshTokenFamilyRevoked when entries exist but every member is revoked.
func (*Store) GetAuthorizationCode ¶
func (s *Store) GetAuthorizationCode(_ context.Context, code string) (*storage.AuthorizationCode, error)
GetAuthorizationCode retrieves an authorization code without modifying it. The code is kept marked as "Used" to detect reuse attempts (OAuth 2.1 requirement). Expired/used codes are cleaned up by the background cleanup goroutine.
NOTE: For actual code exchange, use AtomicCheckAndMarkAuthCodeUsed instead to prevent race conditions.
func (*Store) GetAuthorizationState ¶
func (s *Store) GetAuthorizationState(_ context.Context, stateID string) (*storage.AuthorizationState, error)
GetAuthorizationState retrieves an authorization state by client state
func (*Store) GetAuthorizationStateByProviderState ¶
func (s *Store) GetAuthorizationStateByProviderState(_ context.Context, providerState string) (*storage.AuthorizationState, error)
GetAuthorizationStateByProviderState retrieves an authorization state by provider state This is used during provider callback validation (separate from client state)
func (*Store) GetRefreshTokenFamily ¶
func (s *Store) GetRefreshTokenFamily(_ context.Context, refreshToken string) (*storage.RefreshTokenFamilyMetadata, error)
GetRefreshTokenFamily retrieves family metadata for a refresh token
func (*Store) GetRefreshTokenFamilyByID ¶ added in v0.2.120
func (s *Store) GetRefreshTokenFamilyByID(_ context.Context, familyID string) (*storage.RefreshTokenFamilyMetadata, error)
GetRefreshTokenFamilyByID returns family metadata indexed by family ID (storage.RefreshTokenFamilyByIDStore). The internal map is keyed by refresh token, so this is a linear scan over an entry count bounded by hardMaxFamilyMetadataEntries (50000). Cheap in practice; if it ever shows up in a profile, add a parallel familyID -> refreshToken index.
func (*Store) GetRefreshTokenInfo ¶
GetRefreshTokenInfo retrieves the user ID for a refresh token Returns error if token is not found or expired (with clock skew grace)
func (*Store) GetTokenMetadata ¶ added in v0.1.34
func (s *Store) GetTokenMetadata(tokenID string) (*storage.TokenMetadata, error)
GetTokenMetadata retrieves metadata for a token (including RFC 8707 audience)
func (*Store) GetTokensByUserClient ¶ added in v0.1.9
GetTokensByUserClient retrieves all token IDs for a user+client combination. This is primarily for testing and debugging purposes.
func (*Store) GetUserInfo ¶
GetUserInfo retrieves user information
func (*Store) IsJTIRevoked ¶ added in v0.2.120
IsJTIRevoked implements storage.RevokedTokenStore.
func (*Store) ListClients ¶
ListClients lists all registered clients
func (*Store) LogValue ¶ added in v0.2.123
LogValue implements slog.LogValuer so callers can emit a one-shot structured snapshot of the store's posture:
logger.Info("storage initialized", "store", store)
func (*Store) RevokeAllTokensForUserClient ¶ added in v0.1.9
func (s *Store) RevokeAllTokensForUserClient(_ context.Context, userID, clientID string) (int, error)
RevokeAllTokensForUserClient revokes all tokens (access + refresh) for a specific user+client combination. This implements the OAuth 2.1 requirement for authorization code reuse detection. Returns the number of tokens revoked and any error encountered.
func (*Store) RevokeRefreshTokenFamily ¶
RevokeRefreshTokenFamily revokes all tokens in a family (for reuse detection) This is called when token reuse is detected (OAuth 2.1 security requirement)
func (*Store) SaveAuthorizationCode ¶
SaveAuthorizationCode saves an issued authorization code
func (*Store) SaveAuthorizationState ¶
SaveAuthorizationState saves the state of an ongoing authorization flow Stores by both client state (StateID) and provider state (ProviderState) for dual lookup
func (*Store) SaveClient ¶
SaveClient saves a registered client and tracks IP for DoS protection
func (*Store) SaveRefreshToken ¶
func (s *Store) SaveRefreshToken(_ context.Context, refreshToken, userID string, expiresAt time.Time) error
SaveRefreshToken saves a refresh token mapping to user ID with expiry For OAuth 2.1 compliance, also tracks token family for reuse detection
func (*Store) SaveRefreshTokenWithFamily ¶
func (s *Store) SaveRefreshTokenWithFamily(_ context.Context, refreshToken, userID, clientID, familyID string, generation int, expiresAt time.Time) error
SaveRefreshTokenWithFamily saves a refresh token with family tracking for reuse detection This is the OAuth 2.1 compliant version that enables token theft detection
func (*Store) SaveTokenMetadata ¶ added in v0.1.9
func (s *Store) SaveTokenMetadata(_ context.Context, tokenID string, metadata storage.TokenMetadata) error
SaveTokenMetadata saves metadata for a token. Implements storage.TokenMetadataStore. IssuedAt and ExpiresAt are set by the caller and persisted as-is.
func (*Store) SaveUserInfo ¶
SaveUserInfo saves user information
func (*Store) TrackClientIP ¶
TrackClientIP increments the registration count for the given IP address. The clientID parameter is accepted for interface compatibility but is not persisted by the in-memory store.