Documentation
¶
Index ¶
- Constants
- Variables
- func AllBuiltinScopes() []string
- func ClientIP(r *http.Request, trusted TrustedProxies) string
- func ComputeCodeChallenge(verifier string) string
- func ContainsAllScopes(granted, required []string) bool
- func ContainsScope(scopes []string, scope string) bool
- func GenerateAPIKeyID() (string, error)
- func GenerateAPIKeySecret() (string, error)
- func GenerateSecureToken() (string, error)
- func GetSubjectFromContext(ctx context.Context) string
- func IntersectScopes(requested, allowed []string) []string
- func JoinScopes(scopes []string) string
- func ParseScopes(scopeString string) []string
- func ScopesEqual(a, b []string) bool
- func SetSubjectInContext(ctx context.Context, subject string) context.Context
- func UnionScopes(a, b []string) []string
- func UpperUserCode(s string) string
- func ValidateAll(details []AuthorizationDetail) error
- func ValidateRequestedScopes(requested, allowed []string) (valid, invalid []string)
- func VerifyPKCE(method, challenge, verifier string) bool
- type APIKey
- type APIKeyStore
- type AccountLockout
- type AppRegistration
- type AppRegistrationStore
- type ApproveDeviceAuthorizationRequest
- type ApproveDeviceAuthorizationResponse
- type AuthorizationCode
- type AuthorizationCodeStore
- type AuthorizationDetail
- type CleanupExpiredAuthorizationCodesRequest
- type CleanupExpiredAuthorizationCodesResponse
- type CleanupExpiredDeviceAuthsRequest
- type CleanupExpiredDeviceAuthsResponse
- type CleanupExpiredPushedRequestsRequest
- type CleanupExpiredPushedRequestsResponse
- type CleanupExpiredTokensRequest
- type CleanupExpiredTokensResponse
- type Confirmation
- type ConsumeAuthorizationCodeRequest
- type ConsumeAuthorizationCodeResponse
- type ConsumePushedAuthorizationRequestRequest
- type ConsumePushedAuthorizationRequestResponse
- type CreateAPIKeyRequest
- type CreateAPIKeyResponse
- type CreateAuthorizationCodeRequest
- type CreateAuthorizationCodeResponse
- type CreateDeviceAuthorizationRequest
- type CreateDeviceAuthorizationResponse
- type CreatePushedAuthorizationRequestRequest
- type CreatePushedAuthorizationRequestResponse
- type CreateRefreshTokenRequest
- type CreateRefreshTokenResponse
- type DeleteAppRequest
- type DeleteAppResponse
- type DeleteAuthorizationCodeRequest
- type DeleteAuthorizationCodeResponse
- type DeleteDeviceAuthorizationRequest
- type DeleteDeviceAuthorizationResponse
- type DeletePushedAuthorizationRequestRequest
- type DeletePushedAuthorizationRequestResponse
- type DenyDeviceAuthorizationRequest
- type DenyDeviceAuthorizationResponse
- type DeviceAuthorization
- type DeviceAuthorizationStatus
- type DeviceAuthorizationStore
- type GetAPIKeyByIDRequest
- type GetAPIKeyByIDResponse
- type GetAppRequest
- type GetAppResponse
- type GetAuthorizationCodeRequest
- type GetAuthorizationCodeResponse
- type GetByDeviceCodeRequest
- type GetByDeviceCodeResponse
- type GetByUserCodeRequest
- type GetByUserCodeResponse
- type GetPushedAuthorizationRequestRequest
- type GetPushedAuthorizationRequestResponse
- type GetRefreshTokenRequest
- type GetRefreshTokenResponse
- type GetSubjectScopesFunc
- type GetSubjectTokensRequest
- type GetSubjectTokensResponse
- type InMemoryAppStore
- func (s *InMemoryAppStore) DeleteApp(ctx context.Context, req *DeleteAppRequest) (*DeleteAppResponse, error)
- func (s *InMemoryAppStore) GetApp(ctx context.Context, req *GetAppRequest) (*GetAppResponse, error)
- func (s *InMemoryAppStore) ListApps(ctx context.Context, req *ListAppsRequest) (*ListAppsResponse, error)
- func (s *InMemoryAppStore) SaveApp(ctx context.Context, req *SaveAppRequest) (*SaveAppResponse, error)
- type InMemoryAuthorizationCodeStore
- func (s *InMemoryAuthorizationCodeStore) CleanupExpired(_ context.Context, _ *CleanupExpiredAuthorizationCodesRequest) (*CleanupExpiredAuthorizationCodesResponse, error)
- func (s *InMemoryAuthorizationCodeStore) ConsumeAuthorizationCode(_ context.Context, req *ConsumeAuthorizationCodeRequest) (*ConsumeAuthorizationCodeResponse, error)
- func (s *InMemoryAuthorizationCodeStore) CreateAuthorizationCode(_ context.Context, req *CreateAuthorizationCodeRequest) (*CreateAuthorizationCodeResponse, error)
- func (s *InMemoryAuthorizationCodeStore) DeleteAuthorizationCode(_ context.Context, req *DeleteAuthorizationCodeRequest) (*DeleteAuthorizationCodeResponse, error)
- func (s *InMemoryAuthorizationCodeStore) GetAuthorizationCode(_ context.Context, req *GetAuthorizationCodeRequest) (*GetAuthorizationCodeResponse, error)
- func (s *InMemoryAuthorizationCodeStore) RecordAuthorizationCodeTokens(_ context.Context, req *RecordAuthorizationCodeTokensRequest) (*RecordAuthorizationCodeTokensResponse, error)
- type InMemoryBlacklist
- type InMemoryDeviceAuthorizationStore
- func (s *InMemoryDeviceAuthorizationStore) ApproveDeviceAuthorization(_ context.Context, req *ApproveDeviceAuthorizationRequest) (*ApproveDeviceAuthorizationResponse, error)
- func (s *InMemoryDeviceAuthorizationStore) CleanupExpired(_ context.Context, _ *CleanupExpiredDeviceAuthsRequest) (*CleanupExpiredDeviceAuthsResponse, error)
- func (s *InMemoryDeviceAuthorizationStore) CreateDeviceAuthorization(_ context.Context, req *CreateDeviceAuthorizationRequest) (*CreateDeviceAuthorizationResponse, error)
- func (s *InMemoryDeviceAuthorizationStore) DeleteDeviceAuthorization(_ context.Context, req *DeleteDeviceAuthorizationRequest) (*DeleteDeviceAuthorizationResponse, error)
- func (s *InMemoryDeviceAuthorizationStore) DenyDeviceAuthorization(_ context.Context, req *DenyDeviceAuthorizationRequest) (*DenyDeviceAuthorizationResponse, error)
- func (s *InMemoryDeviceAuthorizationStore) GetByDeviceCode(_ context.Context, req *GetByDeviceCodeRequest) (*GetByDeviceCodeResponse, error)
- func (s *InMemoryDeviceAuthorizationStore) GetByUserCode(_ context.Context, req *GetByUserCodeRequest) (*GetByUserCodeResponse, error)
- func (s *InMemoryDeviceAuthorizationStore) UpdatePollingState(_ context.Context, req *UpdatePollingStateRequest) (*UpdatePollingStateResponse, error)
- type InMemoryPushedAuthorizationRequestStore
- func (s *InMemoryPushedAuthorizationRequestStore) CleanupExpiredPushedRequests(_ context.Context, _ *CleanupExpiredPushedRequestsRequest) (*CleanupExpiredPushedRequestsResponse, error)
- func (s *InMemoryPushedAuthorizationRequestStore) ConsumePushedAuthorizationRequest(_ context.Context, req *ConsumePushedAuthorizationRequestRequest) (*ConsumePushedAuthorizationRequestResponse, error)
- func (s *InMemoryPushedAuthorizationRequestStore) CreatePushedAuthorizationRequest(_ context.Context, req *CreatePushedAuthorizationRequestRequest) (*CreatePushedAuthorizationRequestResponse, error)
- func (s *InMemoryPushedAuthorizationRequestStore) DeletePushedAuthorizationRequest(_ context.Context, req *DeletePushedAuthorizationRequestRequest) (*DeletePushedAuthorizationRequestResponse, error)
- func (s *InMemoryPushedAuthorizationRequestStore) GetPushedAuthorizationRequest(_ context.Context, req *GetPushedAuthorizationRequestRequest) (*GetPushedAuthorizationRequestResponse, error)
- type InMemoryRateLimiter
- type ListAppsRequest
- type ListAppsResponse
- type ListSubjectAPIKeysRequest
- type ListSubjectAPIKeysResponse
- type PushedAuthorizationRequest
- type PushedAuthorizationRequestStore
- type RateLimiter
- type RecordAuthorizationCodeTokensRequest
- type RecordAuthorizationCodeTokensResponse
- type RefreshToken
- type RefreshTokenStore
- type RevokeAPIKeyRequest
- type RevokeAPIKeyResponse
- type RevokeRefreshTokenRequest
- type RevokeRefreshTokenResponse
- type RevokeSubjectTokensRequest
- type RevokeSubjectTokensResponse
- type RevokeTokenFamilyRequest
- type RevokeTokenFamilyResponse
- type RotateRefreshTokenRequest
- type RotateRefreshTokenResponse
- type SaveAppRequest
- type SaveAppResponse
- type TokenBlacklist
- type TokenError
- type TokenPair
- type TokenRequest
- type TrustedProxies
- type UpdateAPIKeyLastUsedRequest
- type UpdateAPIKeyLastUsedResponse
- type UpdatePollingStateRequest
- type UpdatePollingStateResponse
- type ValidateAPIKeyRequest
- type ValidateAPIKeyResponse
Constants ¶
const ( CodeChallengeMethodS256 = "S256" CodeChallengeMethodPlain = "plain" )
PKCE code_challenge_method values per RFC 7636 §4.3.
S256 — BASE64URL(SHA256(verifier)). RFC 7636 §4.2; OAuth 2.1 §7.5 mandates this as the only method for new deployments. Default.
plain — challenge == verifier. RFC 7636 §4.4 permits this for environments that can't compute SHA-256. OAuth 2.1 §7.5 retired it; OneAuth's `/authorize` rejects plain unless the AS opts in via OneAuthConfig.AllowPlainPKCE (capability-gating umbrella #344). VerifyPKCE accepts both methods — gating happens at the authorization endpoint, not at code redemption, so that an AS whose flag is later disabled can still verify codes minted while it was on (codes have a short TTL, the window closes quickly).
Lives in core/ (not the oauth2/ sub-module) so apiauth + downstream sub-modules can share the helper without taking a dep on the browser-side OAuth helper module.
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 ( TokenExpiryAccessToken = 15 * time.Minute // 15 minutes TokenExpiryRefreshToken = 7 * 24 * time.Hour // 7 days )
Default token expiry durations for OAuth tokens.
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.
const PushedRequestURIPrefix = "urn:ietf:params:oauth:request_uri:"
PushedRequestURIPrefix is the RFC 9126 §2.2 URN form for a request_uri. The spec leaves the format to the server but suggests this shape, and using it makes a pushed reference recognizable in logs and in a browser URL without revealing anything about the request it stands for.
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 ErrAppNotFound = errors.New("app registration not found")
ErrAppNotFound is returned by AppRegistrationStore.GetApp and DeleteApp when the requested client_id does not exist.
var ErrAuthorizationCodeNotFound = errors.New("authorization code not found")
ErrAuthorizationCodeNotFound is returned by AuthorizationCodeStore.Get and Delete when no record matches the supplied code. Distinct from "expired" — an expired record may still exist in the store until CleanupExpired runs, and the token-endpoint redemption handler must surface a different error to the caller in each case.
var ErrAuthorizationCodeUsed = errors.New("authorization code already used")
ErrAuthorizationCodeUsed is returned by AuthorizationCodeStore.ConsumeAuthorizationCode when the code was already redeemed. The response still carries the record, with the tokens the first redemption recorded, so the caller can revoke them (RFC 6749 §4.1.2).
var ErrDeviceAuthorizationNotFound = errors.New("device authorization not found")
ErrDeviceAuthorizationNotFound is returned when a lookup by device_code, user_code, or both fails to find a record. Distinct from "expired" — an expired record may still exist in the store until CleanupExpired runs, and callers may want to surface a different error to the caller in each case.
var ErrInvalidAuthorizationDetails = fmt.Errorf("invalid_authorization_details")
ErrInvalidAuthorizationDetails is the OAuth error for malformed or disallowed authorization_details values (RFC 9396 §5.2).
var ErrPushedRequestNotFound = errors.New("pushed authorization request not found")
ErrPushedRequestNotFound is returned by PushedAuthorizationRequestStore lookups and deletes when no record matches the supplied request_uri. Distinct from "expired": an expired record may still sit in the store until CleanupExpired runs, and the authorization endpoint owes the client a different error in each case.
Functions ¶
func AllBuiltinScopes ¶
func AllBuiltinScopes() []string
AllBuiltinScopes returns all built-in scope values
func ClientIP ¶ added in v0.1.45
func ClientIP(r *http.Request, trusted TrustedProxies) string
ClientIP returns the address a request came from, for rate limiting and logging. X-Forwarded-For is client-supplied, so it is read only when the TCP peer is a trusted proxy (CWE-348); otherwise anyone could put a fresh address in every request and get a fresh rate-limit bucket each time.
Behind trusted proxies the header is walked right to left, skipping trusted hops, and the first address that isn't a trusted proxy is the client. Entries to its left were written by the client and are ignored. X-Real-IP is used only when a trusted peer sent no X-Forwarded-For.
func ComputeCodeChallenge ¶ added in v0.1.30
ComputeCodeChallenge computes the S256 code challenge from a verifier per RFC 7636 §4.2: BASE64URL(SHA256(code_verifier)).
Lives here so server-side authorization-code minting (apiauth) and any client-side PKCE driver can share the exact same transformation.
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 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 GetSubjectFromContext ¶ added in v0.1.4
GetSubjectFromContext retrieves the authenticated subject from the request context. Uses the default key DefaultSubjectParamName.
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 SetSubjectInContext ¶ added in v0.1.4
SetSubjectInContext sets the authenticated subject 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 UpperUserCode ¶ added in v0.1.30
UpperUserCode normalizes an RFC 8628 user_code to the canonical store-key form: dashes and spaces stripped, ASCII letters folded to upper case. The display form keeps a dash (XXXX-XXXX) so the printable code stays readable on the device's screen, but every store backend indexes on the normalized form so the user can paste either variant into the verification page and still hit the same record.
RFC 8628 §6.1 leaves user-facing display formatting to the verification UI; the server-side normalization rule is OneAuth's contract — every DeviceAuthorizationStore implementation calls this helper on both the write path (Create) and every read path (GetByUserCode / Approve / Deny). Adding a new backend? Use this function — do not roll your own.
Pure / safe for concurrent use.
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
func VerifyPKCE ¶ added in v0.1.30
VerifyPKCE implements RFC 7636 §4.6 verification. Returns true on match. Handles both S256 (BASE64URL(SHA256(verifier)) compared constant-time to the stored challenge) and plain (verifier == challenge, constant-time). Any other method returns false.
Verification is method-agnostic by design: the OAuth 2.1 §7.5 rejection of plain happens at the authorization endpoint (gated by AuthorizationHandler.AllowPlainPKCE). At redemption time the stored method tells the granter which transformation the client applied. This keeps codes minted under an earlier flag setting verifiable even if the operator later flips AllowPlainPKCE off — codes have a short TTL, so any inconsistency window closes quickly.
Constant-time compare on both branches keeps the hot path's timing flat. The challenge is not secret, but constant-time removes one shape of side-channel concern.
Lives in core/ alongside ComputeCodeChallenge so the PKCE surface is centralized in one transport-agnostic home. apiauth's authorization-code redemption handler is the primary caller.
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.
type APIKeyStore ¶
type APIKeyStore interface {
// CreateAPIKey creates a new API key for the given subject. The full key
// (keyID + "_" + secret) is only returned here.
CreateAPIKey(ctx context.Context, req *CreateAPIKeyRequest) (*CreateAPIKeyResponse, error)
// GetAPIKeyByID retrieves an API key by its public ID.
GetAPIKeyByID(ctx context.Context, req *GetAPIKeyByIDRequest) (*GetAPIKeyByIDResponse, error)
// ValidateAPIKey validates a full API key and returns the key metadata if valid.
ValidateAPIKey(ctx context.Context, req *ValidateAPIKeyRequest) (*ValidateAPIKeyResponse, error)
// RevokeAPIKey marks an API key as revoked.
RevokeAPIKey(ctx context.Context, req *RevokeAPIKeyRequest) (*RevokeAPIKeyResponse, error)
// ListSubjectAPIKeys returns all API keys owned by a subject (without secrets).
ListSubjectAPIKeys(ctx context.Context, req *ListSubjectAPIKeysRequest) (*ListSubjectAPIKeysResponse, error)
// UpdateAPIKeyLastUsed updates the last-used timestamp.
UpdateAPIKeyLastUsed(ctx context.Context, req *UpdateAPIKeyLastUsedRequest) (*UpdateAPIKeyLastUsedResponse, 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 AppRegistration ¶ added in v0.1.9
type AppRegistration struct {
ClientID string `json:"client_id"`
ClientDomain string `json:"client_domain"`
SigningAlg string `json:"signing_alg"`
AuthorizationDetailsTypes []string `json:"authorization_details_types,omitempty"` // RFC 9396
CreatedAt time.Time `json:"created_at"`
Revoked bool `json:"revoked"`
// RFC 7591 / 7592 client metadata (populated by DCR; see issue 157).
ClientName string `json:"client_name,omitempty"`
ClientURI string `json:"client_uri,omitempty"`
RedirectURIs []string `json:"redirect_uris,omitempty"`
GrantTypes []string `json:"grant_types,omitempty"`
Scope string `json:"scope,omitempty"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
// RFC 7592 management credentials. Issued when the management protocol
// is implemented (issue 168); empty for legacy registrations.
RegistrationAccessToken string `json:"registration_access_token,omitempty"`
RegistrationClientURI string `json:"registration_client_uri,omitempty"`
// OIDC Back-Channel Logout 1.0 — per-client push endpoint. When set, the
// AS POSTs a signed logout_token to BackchannelLogoutURI whenever a
// session that touched this client is revoked (issue 261).
// BackchannelLogoutSessionRequired toggles whether the logout_token MUST
// carry a sid claim — true means the client tracks sessions per spec §2.2.
// Empty BackchannelLogoutURI disables dispatch for this client.
BackchannelLogoutURI string `json:"backchannel_logout_uri,omitempty"`
BackchannelLogoutSessionRequired bool `json:"backchannel_logout_session_required,omitempty"`
// DPoPBoundAccessTokens is the RFC 9449 §5.2 client metadata: when
// true, the token endpoint refuses this client's token requests that
// carry no DPoP proof, so every token it receives is sender-constrained.
// False leaves DPoP per-request opt-in for this client, unless
// OneAuthConfig.RequireDPoP demands it of every client.
DPoPBoundAccessTokens bool `json:"dpop_bound_access_tokens,omitempty"`
// RequirePushedAuthorizationRequests is the RFC 9126 §6 client
// metadata: when true, /authorize refuses this client's requests that
// don't arrive through PAR, the per-client form of
// AuthorizationHandler.RequirePushedRequests.
RequirePushedAuthorizationRequests bool `json:"require_pushed_authorization_requests,omitempty"`
}
AppRegistration holds metadata about a registered App (RFC 7591 client).
Lives in core/ so storage backends (stores/fs, stores/gorm, stores/gae) can implement AppRegistrationStore without importing admin/. The DCR / RFC 7592 management metadata fields are persisted starting in issue 165 so that the schema is stable as RFC 7592 management endpoints (issue 168/169/170) populate them. They may be empty for legacy registrations.
type AppRegistrationStore ¶ added in v0.1.9
type AppRegistrationStore interface {
// SaveApp inserts or replaces the registration for req.App.ClientID.
SaveApp(ctx context.Context, req *SaveAppRequest) (*SaveAppResponse, error)
// GetApp returns the registration for req.ClientID, or ErrAppNotFound.
GetApp(ctx context.Context, req *GetAppRequest) (*GetAppResponse, error)
// ListApps returns every registration in the store. Order is unspecified.
ListApps(ctx context.Context, req *ListAppsRequest) (*ListAppsResponse, error)
// DeleteApp removes the registration for req.ClientID. Returns ErrAppNotFound
// if no such registration exists.
DeleteApp(ctx context.Context, req *DeleteAppRequest) (*DeleteAppResponse, error)
}
AppRegistrationStore persists app registration metadata.
Source of truth for registered apps; admin.AppRegistrar holds a hot-path in-memory cache that is hydrated from the store on construction and updated on every write.
Backends: InMemoryAppStore (below), FSAppStore (stores/fs/), GORMAppStore (stores/gorm/), and any future Datastore-backed implementation (issue 228).
type ApproveDeviceAuthorizationRequest ¶ added in v0.1.23
type ApproveDeviceAuthorizationRequest struct {
UserCode string
ApprovedSubject string
GrantedScopes []string // optional — overrides Authorization.Scopes when non-nil (consent UI may narrow scope)
}
ApproveDeviceAuthorizationRequest carries the user_code the user entered plus the subject and scopes that should be bound to the resulting access token. The store transitions Status to Approved.
type ApproveDeviceAuthorizationResponse ¶ added in v0.1.23
type ApproveDeviceAuthorizationResponse struct {
Authorization *DeviceAuthorization
}
ApproveDeviceAuthorizationResponse wraps the updated authorization so callers (e.g. a UI handler that renders a "you may now return to your device" page) can read the subject + scope set that was bound.
type AuthorizationCode ¶ added in v0.1.30
type AuthorizationCode struct {
// Code is the high-entropy identifier the client redeems with.
// 256-bit, hex-encoded; unguessable.
Code string `json:"code"`
// ClientID is the OAuth client the code was issued to. RFC 6749
// §4.1.3 requires the redemption request's authenticated client_id
// to match this value.
ClientID string `json:"client_id"`
// RedirectURI is the verbatim redirect_uri parameter from the
// initial /authorize request. The redemption handler requires the
// token request to send the same value (RFC 6749 §4.1.3).
RedirectURI string `json:"redirect_uri"`
// Scopes is the granted scope set, carried forward unchanged to the
// issued access token. The consent UI may narrow this set relative
// to the requested scopes.
Scopes []string `json:"scopes,omitempty"`
// Subject is the principal (RFC 7519 `sub`) who approved the
// authorization. Bound to the issued access token.
Subject string `json:"subject"`
// CodeChallenge is the PKCE challenge (RFC 7636 §4.2) the client
// committed to at /authorize time. Empty when the client did not
// supply PKCE; the apiauth handler rejects empty challenges by
// default (OAuth 2.1 makes PKCE mandatory for public clients and
// SHOULD-mandatory for all clients).
CodeChallenge string `json:"code_challenge,omitempty"`
// DPoPJKT is the RFC 7638 thumbprint of the DPoP key the client
// named in the `dpop_jkt` authorization request parameter, empty
// when the client sent none (RFC 9449 §10).
//
// When set, the token request redeeming this code MUST carry a DPoP
// proof for the same key. That extends the binding back across the
// front channel: without it the key first appears at the token
// endpoint, so an intercepted code can be redeemed with the
// attacker's key and yields a token bound to the attacker.
//
// The protection only holds when the client uses a fresh key per
// authorization request, which §10 states plainly. A client reusing
// one key gains nothing here that PKCE does not already give it.
DPoPJKT string `json:"dpop_jkt,omitempty"`
// CodeChallengeMethod is the transformation the client applied to
// the verifier — "S256" (RFC 7636 §4.2) or "plain". Only S256 is
// advertised in AS metadata; "plain" is rejected by the apiauth
// handler.
CodeChallengeMethod string `json:"code_challenge_method,omitempty"`
// AuthorizationDetails carries the RFC 9396 fine-grained
// authorization payload from /authorize through to the issued
// access token. Nil when the request had no authorization_details.
AuthorizationDetails []AuthorizationDetail `json:"authorization_details,omitempty"`
// IssuedAt is when the AS minted the code. Useful for telemetry
// and as the anchor for any future "max code age" enforcement.
IssuedAt time.Time `json:"issued_at"`
// ExpiresAt is the deadline after which a redemption returns
// invalid_grant. RFC 6749 §4.1.2 recommends ≤10 minutes; the
// apiauth handler defaults to 60s (OAuth 2.1 alignment).
ExpiresAt time.Time `json:"expires_at"`
// UsedAt is when the code was redeemed, zero while it is unused. Set
// once, by ConsumeAuthorizationCode.
UsedAt time.Time `json:"used_at,omitempty"`
// IssuedTokenID is the `jti` of the access token the redemption
// minted, and IssuedTokenExpiresAt its expiry. The store keeps a used
// record until IssuedTokenExpiresAt, after which there is nothing
// left to revoke.
IssuedTokenID string `json:"issued_token_id,omitempty"`
IssuedTokenExpiresAt time.Time `json:"issued_token_expires_at,omitempty"`
// IssuedRefreshFamily is the family of the refresh token the
// redemption minted, empty when it minted none.
IssuedRefreshFamily string `json:"issued_refresh_family,omitempty"`
// ReuseDetected is set when the code is presented after it was used.
// The redemption that minted the tokens checks it after recording
// them, so a reuse racing the first redemption still revokes.
ReuseDetected bool `json:"reuse_detected,omitempty"`
}
AuthorizationCode is one issued RFC 6749 §4.1 authorization code, minted by the AS at the end of a successful /authorize consent and redeemed at the token endpoint via `grant_type=authorization_code`.
The struct captures every binding the redemption path enforces: client_id (§4.1.3 "MUST be issued to the requesting client"), redirect_uri (§4.1.3 "MUST be identical to the value included in the initial authorization request"), PKCE challenge (RFC 7636 §4.6), scopes carried forward to the issued access token, and the subject the resource owner consented as.
Codes are single-use. Redemption marks the record used (ConsumeAuthorizationCode) rather than deleting it, and keeps it until the access token it produced expires, so a second redemption can find and revoke that token (RFC 6749 §4.1.2). RFC 6749 §4.1.2 mandates short lifetimes (≤10 minutes); the apiauth handler defaults to 60 seconds, which matches OAuth 2.1.
func (*AuthorizationCode) IsExpired ¶ added in v0.1.30
func (a *AuthorizationCode) IsExpired(now time.Time) bool
IsExpired reports whether the code is past its deadline. Centralized here so the store, the redemption handler, and the cleanup pass agree on the comparison.
func (*AuthorizationCode) IsUsed ¶ added in v0.1.57
func (a *AuthorizationCode) IsUsed() bool
IsUsed reports whether the code has been redeemed.
func (*AuthorizationCode) RetainUntil ¶ added in v0.1.57
func (a *AuthorizationCode) RetainUntil() time.Time
RetainUntil is when CleanupExpired may remove the record: the code's own expiry, or for a used code the later of that and the expiry of the access token it produced.
type AuthorizationCodeStore ¶ added in v0.1.30
type AuthorizationCodeStore interface {
// CreateAuthorizationCode inserts a new authorization code. The
// Code field MUST be unique across all non-expired records — the
// caller has already generated it via cryptographic RNG, so a
// collision is a programmer error and surfaces as a generic error
// (not a typed sentinel). Mirrors the DeviceAuthorizationStore
// collision contract.
CreateAuthorizationCode(ctx context.Context, req *CreateAuthorizationCodeRequest) (*CreateAuthorizationCodeResponse, error)
// GetAuthorizationCode returns the binding for the given code, or
// ErrAuthorizationCodeNotFound. Does NOT filter by expiry — the
// caller (the token endpoint handler) checks IsExpired so it can
// surface a distinct invalid_grant vs expired error.
GetAuthorizationCode(ctx context.Context, req *GetAuthorizationCodeRequest) (*GetAuthorizationCodeResponse, error)
// DeleteAuthorizationCode removes the binding. Called by the token
// endpoint on an expired code and by callers cleaning up rejected
// flows. Returns ErrAuthorizationCodeNotFound when no record matches.
DeleteAuthorizationCode(ctx context.Context, req *DeleteAuthorizationCodeRequest) (*DeleteAuthorizationCodeResponse, error)
// ConsumeAuthorizationCode marks an unused code used and returns the
// record as it was. For a code already used it sets ReuseDetected and
// returns ErrAuthorizationCodeUsed together with the record, whose
// Issued* fields name the tokens to revoke. A missing code is
// ErrAuthorizationCodeNotFound.
ConsumeAuthorizationCode(ctx context.Context, req *ConsumeAuthorizationCodeRequest) (*ConsumeAuthorizationCodeResponse, error)
// RecordAuthorizationCodeTokens stores the tokens a redemption minted
// on the used record and reports ReuseDetected, read in the same
// atomic step. A missing code is ErrAuthorizationCodeNotFound.
RecordAuthorizationCodeTokens(ctx context.Context, req *RecordAuthorizationCodeTokensRequest) (*RecordAuthorizationCodeTokensResponse, error)
// CleanupExpired removes every record whose RetainUntil is at or
// before the current wall-clock time, so a used code outlives its own
// expiry until the access token it produced has expired. Production
// deployments run this on a timer; tests call it explicitly.
CleanupExpired(ctx context.Context, req *CleanupExpiredAuthorizationCodesRequest) (*CleanupExpiredAuthorizationCodesResponse, error)
}
AuthorizationCodeStore persists RFC 6749 §4.1 authorization codes.
Backends ship in lock-step with the other store interfaces: InMemoryAuthorizationCodeStore (below) is the in-process default. FS / GORM / GAE backends follow the same pattern established by DeviceAuthorizationStore (issues 269 / 270) and are deferred to follow-ups.
Redemption is a two-step protocol, both steps atomic per record: ConsumeAuthorizationCode marks the code used (the single-use gate), and RecordAuthorizationCodeTokens stores what the redemption minted. A second Consume flags the record and returns those tokens so the caller can revoke them; a Record that finds the flag tells its caller to revoke its own. Whichever of the two runs second sees the other's write, so a reuse racing the first redemption is still caught.
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 CleanupExpiredAuthorizationCodesRequest ¶ added in v0.1.30
type CleanupExpiredAuthorizationCodesRequest struct{}
CleanupExpiredAuthorizationCodesRequest is empty; the call enumerates the store and removes anything past its expiry.
type CleanupExpiredAuthorizationCodesResponse ¶ added in v0.1.30
type CleanupExpiredAuthorizationCodesResponse struct {
Removed int
}
CleanupExpiredAuthorizationCodesResponse reports how many records were removed. Useful for telemetry / log-line metrics.
type CleanupExpiredDeviceAuthsRequest ¶ added in v0.1.23
type CleanupExpiredDeviceAuthsRequest struct{}
CleanupExpiredDeviceAuthsRequest is empty; the call enumerates the store and removes anything past its expiry.
type CleanupExpiredDeviceAuthsResponse ¶ added in v0.1.23
type CleanupExpiredDeviceAuthsResponse struct {
Removed int
}
CleanupExpiredDeviceAuthsResponse reports how many records were removed. Useful for telemetry / log-line metrics.
type CleanupExpiredPushedRequestsRequest ¶ added in v0.1.39
type CleanupExpiredPushedRequestsRequest struct{}
CleanupExpiredPushedRequestsRequest is empty; the call enumerates the store and drops anything past its expiry.
type CleanupExpiredPushedRequestsResponse ¶ added in v0.1.39
type CleanupExpiredPushedRequestsResponse struct {
Removed int
}
CleanupExpiredPushedRequestsResponse reports how many records went.
type CleanupExpiredTokensRequest ¶ added in v0.1.7
type CleanupExpiredTokensRequest struct{}
type CleanupExpiredTokensResponse ¶ added in v0.1.7
type CleanupExpiredTokensResponse struct{}
type Confirmation ¶ added in v0.1.37
type Confirmation struct {
JKT string `json:"jkt,omitempty"`
}
Confirmation is the RFC 7800 `cnf` claim: the proof-of-possession key a token is bound to. A token carrying a Confirmation is sender-constrained — presenting it is not enough, the presenter must also prove possession of the named key.
One field per binding mechanism, all optional, at most one set in practice:
- JKT is the RFC 7638 thumbprint of the client's DPoP public key (RFC 9449 §6). The client proves possession by signing a DPoP proof JWT with the matching private key.
mTLS certificate binding (RFC 8705, `x5t#S256`) is the other member of this family and lands as a second field when #335 ships — that is why this is a struct rather than a bare thumbprint string.
A nil *Confirmation means the token is a plain bearer token: whoever holds it can use it.
See: https://www.rfc-editor.org/rfc/rfc7800 (cnf semantics) See: https://www.rfc-editor.org/rfc/rfc9449#section-6 (cnf.jkt)
func (*Confirmation) Equal ¶ added in v0.1.37
func (c *Confirmation) Equal(other *Confirmation) bool
Equal reports whether two confirmations name the same key. Nil and empty compare equal to each other, so an unbound token matches an unbound presentation. Used on the refresh grant to check that the key presented with a rotation request is the key the refresh token was issued to.
func (*Confirmation) IsEmpty ¶ added in v0.1.37
func (c *Confirmation) IsEmpty() bool
IsEmpty reports whether the confirmation names no key at all. A nil receiver is empty, so callers can test a token's binding without a nil check of their own. An empty Confirmation must never be emitted as a `cnf` claim — an empty `cnf` object claims a binding that nothing enforces.
type ConsumeAuthorizationCodeRequest ¶ added in v0.1.57
type ConsumeAuthorizationCodeRequest struct {
Code string
}
ConsumeAuthorizationCodeRequest carries the code being redeemed.
type ConsumeAuthorizationCodeResponse ¶ added in v0.1.57
type ConsumeAuthorizationCodeResponse struct {
Code *AuthorizationCode
}
ConsumeAuthorizationCodeResponse carries the record as it was before the call: unused on success, or, with ErrAuthorizationCodeUsed, the first redemption's recorded tokens.
type ConsumePushedAuthorizationRequestRequest ¶ added in v0.1.39
type ConsumePushedAuthorizationRequestRequest struct {
RequestURI string
}
ConsumePushedAuthorizationRequestRequest marks a reference redeemed.
type ConsumePushedAuthorizationRequestResponse ¶ added in v0.1.39
type ConsumePushedAuthorizationRequestResponse struct {
// Consumed is true only for the call that flipped the record from
// unused to used. A second call on the same reference gets false, so
// the caller can refuse it: of two approvals racing on one pushed
// request, only one may issue a code.
Consumed bool
}
ConsumePushedAuthorizationRequestResponse reports whether this call spent the reference.
type CreateAPIKeyRequest ¶ added in v0.1.7
CreateAPIKeyRequest carries the inputs for issuing an API key.
type CreateAPIKeyResponse ¶ added in v0.1.7
CreateAPIKeyResponse carries the issued key. FullKey is keyID + "_" + secret and is only available at creation time.
type CreateAuthorizationCodeRequest ¶ added in v0.1.30
type CreateAuthorizationCodeRequest struct {
Code *AuthorizationCode
}
CreateAuthorizationCodeRequest carries the inputs for persisting a freshly-minted authorization code. The handler computes the code, timestamps, and binding; the store persists.
type CreateAuthorizationCodeResponse ¶ added in v0.1.30
type CreateAuthorizationCodeResponse struct{}
CreateAuthorizationCodeResponse is empty — CreateAuthorizationCode returns success/failure via the error.
type CreateDeviceAuthorizationRequest ¶ added in v0.1.23
type CreateDeviceAuthorizationRequest struct {
Authorization *DeviceAuthorization
}
CreateDeviceAuthorizationRequest carries the inputs for persisting a freshly-minted device authorization. The handler computes the codes, status, and timestamps; the store persists.
type CreateDeviceAuthorizationResponse ¶ added in v0.1.23
type CreateDeviceAuthorizationResponse struct{}
CreateDeviceAuthorizationResponse is empty — CreateDeviceAuthorization returns success/failure via the error.
type CreatePushedAuthorizationRequestRequest ¶ added in v0.1.39
type CreatePushedAuthorizationRequestRequest struct {
Request *PushedAuthorizationRequest
}
CreatePushedAuthorizationRequestRequest carries the record to insert. The caller has already generated RequestURI with a CSPRNG and authenticated the client.
type CreatePushedAuthorizationRequestResponse ¶ added in v0.1.39
type CreatePushedAuthorizationRequestResponse struct {
Request *PushedAuthorizationRequest
}
CreatePushedAuthorizationRequestResponse echoes the stored record.
type CreateRefreshTokenRequest ¶ added in v0.1.7
type CreateRefreshTokenRequest struct {
Subject string
ClientID string
DeviceInfo map[string]any
Scopes []string
// Confirmation binds the refresh token to a proof-of-possession key
// (RFC 9449 §5). Non-nil means only a client that can prove possession
// of that key may redeem it; rotation carries the binding forward.
// Nil issues a plain bearer refresh token.
Confirmation *Confirmation
}
CreateRefreshTokenRequest carries the inputs for issuing a refresh token. Subject is the RFC 7519 `sub` (user ID for user-bound flows, client_id for client_credentials).
type CreateRefreshTokenResponse ¶ added in v0.1.7
type CreateRefreshTokenResponse struct {
Token *RefreshToken
}
type DeleteAppRequest ¶ added in v0.1.9
type DeleteAppRequest struct {
ClientID string
}
DeleteAppRequest carries the client_id to remove.
type DeleteAppResponse ¶ added in v0.1.9
type DeleteAppResponse struct{}
DeleteAppResponse is empty; DeleteApp returns only an error signal.
type DeleteAuthorizationCodeRequest ¶ added in v0.1.30
type DeleteAuthorizationCodeRequest struct {
Code string
}
DeleteAuthorizationCodeRequest carries the code to remove. The redemption handler deletes an expired code it was handed; a redeemed code is marked used instead (ConsumeAuthorizationCode).
type DeleteAuthorizationCodeResponse ¶ added in v0.1.30
type DeleteAuthorizationCodeResponse struct{}
DeleteAuthorizationCodeResponse is empty.
type DeleteDeviceAuthorizationRequest ¶ added in v0.1.23
type DeleteDeviceAuthorizationRequest struct {
DeviceCode string
}
DeleteDeviceAuthorizationRequest carries the device_code to remove. The grant handler deletes after a successful token exchange so the same device_code cannot be replayed for a second access token.
type DeleteDeviceAuthorizationResponse ¶ added in v0.1.23
type DeleteDeviceAuthorizationResponse struct{}
DeleteDeviceAuthorizationResponse is empty.
type DeletePushedAuthorizationRequestRequest ¶ added in v0.1.39
type DeletePushedAuthorizationRequestRequest struct {
RequestURI string
}
DeletePushedAuthorizationRequestRequest removes a record outright.
type DeletePushedAuthorizationRequestResponse ¶ added in v0.1.39
type DeletePushedAuthorizationRequestResponse struct{}
DeletePushedAuthorizationRequestResponse is empty.
type DenyDeviceAuthorizationRequest ¶ added in v0.1.23
type DenyDeviceAuthorizationRequest struct {
UserCode string
}
DenyDeviceAuthorizationRequest carries the user_code the user entered when refusing the authorization.
type DenyDeviceAuthorizationResponse ¶ added in v0.1.23
type DenyDeviceAuthorizationResponse struct{}
DenyDeviceAuthorizationResponse is empty.
type DeviceAuthorization ¶ added in v0.1.23
type DeviceAuthorization struct {
// DeviceCode is the high-entropy identifier the device polls with.
// 256-bit, hex-encoded; unguessable.
DeviceCode string `json:"device_code"`
// UserCode is the short, human-typeable identifier the user enters on
// the verification URI. RFC 8628 §6.1 recommends a constrained
// charset; this implementation uses 8 chars from
// BCDEFGHJKLMNPQRSTVWXZ23456789 (ambiguity-resistant).
UserCode string `json:"user_code"`
// ClientID is the OAuth client that initiated the device flow.
ClientID string `json:"client_id"`
// Scopes is the scope set requested at /device/authorize. Carried
// forward unchanged to the issued access token.
Scopes []string `json:"scopes,omitempty"`
// RequestedAudience captures an optional RFC 8707 `audience` parameter
// the device passed at /device/authorize. Empty when not requested.
RequestedAudience string `json:"requested_audience,omitempty"`
// Status tracks the lifecycle. See DeviceAuthorizationStatus values.
Status DeviceAuthorizationStatus `json:"status"`
// ApprovedSubject is the user (RFC 7519 `sub`) who approved this
// authorization. Populated only when Status == Approved.
ApprovedSubject string `json:"approved_subject,omitempty"`
// CreatedAt is when the device authorization was created. Pinned at
// creation time so polling-interval enforcement can reason from it.
CreatedAt time.Time `json:"created_at"`
// ExpiresAt is the deadline after which a polling request returns
// expired_token. RFC 8628 §3.4 recommends 15 minutes.
ExpiresAt time.Time `json:"expires_at"`
// LastPolledAt is the last time the device polled the token endpoint
// with this device_code. Used to enforce the `interval` rate limit and
// emit `slow_down`.
LastPolledAt time.Time `json:"last_polled_at,omitempty"`
// IntervalSeconds is the current minimum polling interval the device
// MUST respect. Starts at the AS default (5s per RFC 8628 §3.5) and
// may grow when the AS emits `slow_down`.
IntervalSeconds int `json:"interval_seconds"`
}
DeviceAuthorization is one RFC 8628 §3.1 device authorization request. device_code and user_code are independent identifiers — the device polls by device_code, the user enters user_code on the verification URI.
type DeviceAuthorizationStatus ¶ added in v0.1.23
type DeviceAuthorizationStatus string
DeviceAuthorizationStatus is the lifecycle state of an RFC 8628 device authorization request. The token endpoint maps status to the polling error response: Pending → authorization_pending, Denied → access_denied, Expired → expired_token. Approved is the only state that yields an access token.
const ( // DeviceAuthorizationStatusPending means the user has not yet visited // the verification URI (or has visited but not approved). Polling // returns `authorization_pending`. DeviceAuthorizationStatusPending DeviceAuthorizationStatus = "pending" // DeviceAuthorizationStatusApproved means the user approved the // authorization. The next successful poll yields an access token; the // device authorization is consumed (deleted) on a successful token // exchange to prevent replay. DeviceAuthorizationStatusApproved DeviceAuthorizationStatus = "approved" // DeviceAuthorizationStatusDenied means the user explicitly rejected // the authorization. Polling returns `access_denied`. DeviceAuthorizationStatusDenied DeviceAuthorizationStatus = "denied" )
type DeviceAuthorizationStore ¶ added in v0.1.23
type DeviceAuthorizationStore interface {
// CreateDeviceAuthorization inserts a new device authorization.
// device_code and user_code MUST be unique across all non-expired
// records — the caller has already generated them via cryptographic
// RNG, so a collision is a programmer error and surfaces as a
// generic error (not a typed sentinel).
CreateDeviceAuthorization(ctx context.Context, req *CreateDeviceAuthorizationRequest) (*CreateDeviceAuthorizationResponse, error)
// GetByDeviceCode returns the authorization for the given device_code,
// or ErrDeviceAuthorizationNotFound. Does NOT filter by status or
// expiry — the caller (the token endpoint handler) checks both.
GetByDeviceCode(ctx context.Context, req *GetByDeviceCodeRequest) (*GetByDeviceCodeResponse, error)
// GetByUserCode returns the authorization for the given user_code (case-
// insensitive comparison), or ErrDeviceAuthorizationNotFound.
GetByUserCode(ctx context.Context, req *GetByUserCodeRequest) (*GetByUserCodeResponse, error)
// ApproveDeviceAuthorization transitions a pending authorization to
// approved and binds the subject + scopes that the user consented to.
// Returns ErrDeviceAuthorizationNotFound when no pending record matches
// user_code.
ApproveDeviceAuthorization(ctx context.Context, req *ApproveDeviceAuthorizationRequest) (*ApproveDeviceAuthorizationResponse, error)
// DenyDeviceAuthorization transitions a pending authorization to
// denied. Returns ErrDeviceAuthorizationNotFound when no pending
// record matches user_code.
DenyDeviceAuthorization(ctx context.Context, req *DenyDeviceAuthorizationRequest) (*DenyDeviceAuthorizationResponse, error)
// UpdatePollingState records a poll attempt. Always updates
// LastPolledAt; raises IntervalSeconds by 5 when SlowDown is true.
UpdatePollingState(ctx context.Context, req *UpdatePollingStateRequest) (*UpdatePollingStateResponse, error)
// DeleteDeviceAuthorization removes the authorization. Used by the
// token endpoint on successful exchange (prevents replay) and by
// callers cleaning up rejected flows. Returns
// ErrDeviceAuthorizationNotFound when no record matches.
DeleteDeviceAuthorization(ctx context.Context, req *DeleteDeviceAuthorizationRequest) (*DeleteDeviceAuthorizationResponse, error)
// CleanupExpired enumerates the store and removes every record whose
// ExpiresAt is at or before the current wall-clock time. Production
// deployments run this on a timer; tests call it explicitly.
CleanupExpired(ctx context.Context, req *CleanupExpiredDeviceAuthsRequest) (*CleanupExpiredDeviceAuthsResponse, error)
}
DeviceAuthorizationStore persists RFC 8628 device authorization state.
Backends ship in lock-step with the other store interfaces: InMemoryDeviceAuthorizationStore (below), FSDeviceAuthorizationStore (stores/fs/). GORM and GAE backends are deferred to follow-ups under the same precedent as AppRegistrationStore.
Lookups by user_code MUST be case-insensitive (RFC 8628 §3.2) — users enter the code by hand and shift state is irrelevant. Stores normalize to upper-case before comparison.
type GetAPIKeyByIDRequest ¶ added in v0.1.7
type GetAPIKeyByIDRequest struct {
KeyID string
}
type GetAPIKeyByIDResponse ¶ added in v0.1.7
type GetAPIKeyByIDResponse struct {
APIKey *APIKey
}
type GetAppRequest ¶ added in v0.1.9
type GetAppRequest struct {
ClientID string
}
GetAppRequest carries the client_id to look up.
type GetAppResponse ¶ added in v0.1.9
type GetAppResponse struct {
App *AppRegistration
}
GetAppResponse wraps the requested registration.
type GetAuthorizationCodeRequest ¶ added in v0.1.30
type GetAuthorizationCodeRequest struct {
Code string
}
GetAuthorizationCodeRequest carries the code to look up.
type GetAuthorizationCodeResponse ¶ added in v0.1.30
type GetAuthorizationCodeResponse struct {
Code *AuthorizationCode
}
GetAuthorizationCodeResponse wraps the requested code binding.
type GetByDeviceCodeRequest ¶ added in v0.1.23
type GetByDeviceCodeRequest struct {
DeviceCode string
}
GetByDeviceCodeRequest carries the device_code to look up.
type GetByDeviceCodeResponse ¶ added in v0.1.23
type GetByDeviceCodeResponse struct {
Authorization *DeviceAuthorization
}
GetByDeviceCodeResponse wraps the requested authorization.
type GetByUserCodeRequest ¶ added in v0.1.23
type GetByUserCodeRequest struct {
UserCode string
}
GetByUserCodeRequest carries the user_code to look up. user_code is case-insensitive per RFC 8628 §3.2; the store implementation normalizes to upper-case before comparison.
type GetByUserCodeResponse ¶ added in v0.1.23
type GetByUserCodeResponse struct {
Authorization *DeviceAuthorization
}
GetByUserCodeResponse wraps the requested authorization.
type GetPushedAuthorizationRequestRequest ¶ added in v0.1.39
type GetPushedAuthorizationRequestRequest struct {
RequestURI string
}
GetPushedAuthorizationRequestRequest looks a record up by its request_uri.
type GetPushedAuthorizationRequestResponse ¶ added in v0.1.39
type GetPushedAuthorizationRequestResponse struct {
Request *PushedAuthorizationRequest
}
GetPushedAuthorizationRequestResponse wraps the stored record.
type GetRefreshTokenRequest ¶ added in v0.1.7
type GetRefreshTokenRequest struct {
Token string
}
type GetRefreshTokenResponse ¶ added in v0.1.7
type GetRefreshTokenResponse struct {
Token *RefreshToken
}
type GetSubjectScopesFunc ¶ added in v0.1.4
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 GetSubjectTokensRequest ¶ added in v0.1.7
type GetSubjectTokensRequest struct {
Subject string
}
type GetSubjectTokensResponse ¶ added in v0.1.7
type GetSubjectTokensResponse struct {
Tokens []*RefreshToken
}
type InMemoryAppStore ¶ added in v0.1.9
type InMemoryAppStore struct {
// contains filtered or unexported fields
}
InMemoryAppStore is a process-local AppRegistrationStore. State is lost on restart — suitable for tests and dev. Production deployments should use a persistent backend (FS, GORM, GAE).
func NewInMemoryAppStore ¶ added in v0.1.9
func NewInMemoryAppStore() *InMemoryAppStore
NewInMemoryAppStore returns an empty InMemoryAppStore.
func (*InMemoryAppStore) DeleteApp ¶ added in v0.1.9
func (s *InMemoryAppStore) DeleteApp(ctx context.Context, req *DeleteAppRequest) (*DeleteAppResponse, error)
func (*InMemoryAppStore) GetApp ¶ added in v0.1.9
func (s *InMemoryAppStore) GetApp(ctx context.Context, req *GetAppRequest) (*GetAppResponse, error)
func (*InMemoryAppStore) ListApps ¶ added in v0.1.9
func (s *InMemoryAppStore) ListApps(ctx context.Context, req *ListAppsRequest) (*ListAppsResponse, error)
func (*InMemoryAppStore) SaveApp ¶ added in v0.1.9
func (s *InMemoryAppStore) SaveApp(ctx context.Context, req *SaveAppRequest) (*SaveAppResponse, error)
type InMemoryAuthorizationCodeStore ¶ added in v0.1.30
type InMemoryAuthorizationCodeStore struct {
// contains filtered or unexported fields
}
InMemoryAuthorizationCodeStore is a process-local AuthorizationCodeStore. State is lost on restart — suitable for tests, single-process dev, and the in-memory mode of the reference server. Production deployments use a persistent backend.
func NewInMemoryAuthorizationCodeStore ¶ added in v0.1.30
func NewInMemoryAuthorizationCodeStore() *InMemoryAuthorizationCodeStore
NewInMemoryAuthorizationCodeStore returns an empty store.
func (*InMemoryAuthorizationCodeStore) CleanupExpired ¶ added in v0.1.30
func (s *InMemoryAuthorizationCodeStore) CleanupExpired(_ context.Context, _ *CleanupExpiredAuthorizationCodesRequest) (*CleanupExpiredAuthorizationCodesResponse, error)
CleanupExpired removes records past their RetainUntil.
func (*InMemoryAuthorizationCodeStore) ConsumeAuthorizationCode ¶ added in v0.1.57
func (s *InMemoryAuthorizationCodeStore) ConsumeAuthorizationCode(_ context.Context, req *ConsumeAuthorizationCodeRequest) (*ConsumeAuthorizationCodeResponse, error)
ConsumeAuthorizationCode marks the code used under the store's lock; see AuthorizationCodeStore.
func (*InMemoryAuthorizationCodeStore) CreateAuthorizationCode ¶ added in v0.1.30
func (s *InMemoryAuthorizationCodeStore) CreateAuthorizationCode(_ context.Context, req *CreateAuthorizationCodeRequest) (*CreateAuthorizationCodeResponse, error)
func (*InMemoryAuthorizationCodeStore) DeleteAuthorizationCode ¶ added in v0.1.30
func (s *InMemoryAuthorizationCodeStore) DeleteAuthorizationCode(_ context.Context, req *DeleteAuthorizationCodeRequest) (*DeleteAuthorizationCodeResponse, error)
func (*InMemoryAuthorizationCodeStore) GetAuthorizationCode ¶ added in v0.1.30
func (s *InMemoryAuthorizationCodeStore) GetAuthorizationCode(_ context.Context, req *GetAuthorizationCodeRequest) (*GetAuthorizationCodeResponse, error)
func (*InMemoryAuthorizationCodeStore) RecordAuthorizationCodeTokens ¶ added in v0.1.57
func (s *InMemoryAuthorizationCodeStore) RecordAuthorizationCodeTokens(_ context.Context, req *RecordAuthorizationCodeTokensRequest) (*RecordAuthorizationCodeTokensResponse, error)
RecordAuthorizationCodeTokens stores the minted tokens under the store's lock; see AuthorizationCodeStore.
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 InMemoryDeviceAuthorizationStore ¶ added in v0.1.23
type InMemoryDeviceAuthorizationStore struct {
// contains filtered or unexported fields
}
InMemoryDeviceAuthorizationStore is a process-local DeviceAuthorizationStore. State is lost on restart — suitable for tests, single-process dev, and the in-memory mode of the reference server. Production deployments use a persistent backend.
func NewInMemoryDeviceAuthorizationStore ¶ added in v0.1.23
func NewInMemoryDeviceAuthorizationStore() *InMemoryDeviceAuthorizationStore
NewInMemoryDeviceAuthorizationStore returns an empty store.
func (*InMemoryDeviceAuthorizationStore) ApproveDeviceAuthorization ¶ added in v0.1.23
func (s *InMemoryDeviceAuthorizationStore) ApproveDeviceAuthorization(_ context.Context, req *ApproveDeviceAuthorizationRequest) (*ApproveDeviceAuthorizationResponse, error)
func (*InMemoryDeviceAuthorizationStore) CleanupExpired ¶ added in v0.1.23
func (s *InMemoryDeviceAuthorizationStore) CleanupExpired(_ context.Context, _ *CleanupExpiredDeviceAuthsRequest) (*CleanupExpiredDeviceAuthsResponse, error)
func (*InMemoryDeviceAuthorizationStore) CreateDeviceAuthorization ¶ added in v0.1.23
func (s *InMemoryDeviceAuthorizationStore) CreateDeviceAuthorization(_ context.Context, req *CreateDeviceAuthorizationRequest) (*CreateDeviceAuthorizationResponse, error)
func (*InMemoryDeviceAuthorizationStore) DeleteDeviceAuthorization ¶ added in v0.1.23
func (s *InMemoryDeviceAuthorizationStore) DeleteDeviceAuthorization(_ context.Context, req *DeleteDeviceAuthorizationRequest) (*DeleteDeviceAuthorizationResponse, error)
func (*InMemoryDeviceAuthorizationStore) DenyDeviceAuthorization ¶ added in v0.1.23
func (s *InMemoryDeviceAuthorizationStore) DenyDeviceAuthorization(_ context.Context, req *DenyDeviceAuthorizationRequest) (*DenyDeviceAuthorizationResponse, error)
func (*InMemoryDeviceAuthorizationStore) GetByDeviceCode ¶ added in v0.1.23
func (s *InMemoryDeviceAuthorizationStore) GetByDeviceCode(_ context.Context, req *GetByDeviceCodeRequest) (*GetByDeviceCodeResponse, error)
func (*InMemoryDeviceAuthorizationStore) GetByUserCode ¶ added in v0.1.23
func (s *InMemoryDeviceAuthorizationStore) GetByUserCode(_ context.Context, req *GetByUserCodeRequest) (*GetByUserCodeResponse, error)
func (*InMemoryDeviceAuthorizationStore) UpdatePollingState ¶ added in v0.1.23
func (s *InMemoryDeviceAuthorizationStore) UpdatePollingState(_ context.Context, req *UpdatePollingStateRequest) (*UpdatePollingStateResponse, error)
type InMemoryPushedAuthorizationRequestStore ¶ added in v0.1.39
type InMemoryPushedAuthorizationRequestStore struct {
// contains filtered or unexported fields
}
InMemoryPushedAuthorizationRequestStore is a process-local PushedAuthorizationRequestStore. Records are lost on restart, which costs a client one re-push, and are invisible to other processes, which breaks any deployment serving /par and /authorize from more than one node. Use it for tests, single-process dev, and the in-memory mode of the reference server; use a shared backend anywhere else.
func NewInMemoryPushedAuthorizationRequestStore ¶ added in v0.1.39
func NewInMemoryPushedAuthorizationRequestStore() *InMemoryPushedAuthorizationRequestStore
NewInMemoryPushedAuthorizationRequestStore returns an empty store.
func (*InMemoryPushedAuthorizationRequestStore) CleanupExpiredPushedRequests ¶ added in v0.1.39
func (s *InMemoryPushedAuthorizationRequestStore) CleanupExpiredPushedRequests(_ context.Context, _ *CleanupExpiredPushedRequestsRequest) (*CleanupExpiredPushedRequestsResponse, error)
func (*InMemoryPushedAuthorizationRequestStore) ConsumePushedAuthorizationRequest ¶ added in v0.1.39
func (s *InMemoryPushedAuthorizationRequestStore) ConsumePushedAuthorizationRequest(_ context.Context, req *ConsumePushedAuthorizationRequestRequest) (*ConsumePushedAuthorizationRequestResponse, error)
func (*InMemoryPushedAuthorizationRequestStore) CreatePushedAuthorizationRequest ¶ added in v0.1.39
func (s *InMemoryPushedAuthorizationRequestStore) CreatePushedAuthorizationRequest(_ context.Context, req *CreatePushedAuthorizationRequestRequest) (*CreatePushedAuthorizationRequestResponse, error)
func (*InMemoryPushedAuthorizationRequestStore) DeletePushedAuthorizationRequest ¶ added in v0.1.39
func (s *InMemoryPushedAuthorizationRequestStore) DeletePushedAuthorizationRequest(_ context.Context, req *DeletePushedAuthorizationRequestRequest) (*DeletePushedAuthorizationRequestResponse, error)
func (*InMemoryPushedAuthorizationRequestStore) GetPushedAuthorizationRequest ¶ added in v0.1.39
func (s *InMemoryPushedAuthorizationRequestStore) GetPushedAuthorizationRequest(_ context.Context, req *GetPushedAuthorizationRequestRequest) (*GetPushedAuthorizationRequestResponse, error)
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 ListAppsRequest ¶ added in v0.1.9
type ListAppsRequest struct{}
ListAppsRequest is empty; ListApps takes no parameters.
type ListAppsResponse ¶ added in v0.1.9
type ListAppsResponse struct {
Apps []*AppRegistration
}
ListAppsResponse wraps every registration in the store. Order is unspecified.
type ListSubjectAPIKeysRequest ¶ added in v0.1.7
type ListSubjectAPIKeysRequest struct {
Subject string
}
type ListSubjectAPIKeysResponse ¶ added in v0.1.7
type ListSubjectAPIKeysResponse struct {
APIKeys []*APIKey
}
type PushedAuthorizationRequest ¶ added in v0.1.39
type PushedAuthorizationRequest struct {
RequestURI string `json:"request_uri"`
ClientID string `json:"client_id"`
Payload url.Values `json:"payload"`
Consumed bool `json:"consumed"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at"`
}
PushedAuthorizationRequest is one authorization request a client pushed to the PAR endpoint (RFC 9126) and will later reference by request_uri from the browser.
Payload holds the pushed parameters verbatim rather than a parsed struct. §2.1 admits "any of the parameters applicable for use at the authorization endpoint, including those defined in [RFC6749] as well as all applicable extensions", so anything this server does not itself understand still has to survive the round trip: RFC 9396 `authorization_details` JSON and RFC 8707 `resource` are the live cases, and keeping large RAR payloads out of the browser URL is much of why PAR exists.
ClientID records the client the AS authenticated at the push. §2.2 requires the request_uri to be bound to that client, which is what stops one client from redeeming a reference another client pushed.
Consumed marks a reference whose authorization code has been issued. §4 wants a request_uri used once, but explicitly allows a server to tolerate a repeated dereference caused by a browser reload. Consumption therefore tracks code issuance rather than lookups: the built-in consent screen reads the record at least twice (render, then approve), and a user who reloads before approving is doing nothing wrong.
type PushedAuthorizationRequestStore ¶ added in v0.1.39
type PushedAuthorizationRequestStore interface {
// CreatePushedAuthorizationRequest inserts a new record. RequestURI
// MUST be unique among live records. The caller generates it with a
// CSPRNG, so a collision is a programmer error and surfaces as a
// generic error rather than a typed sentinel, matching the
// AuthorizationCodeStore contract.
CreatePushedAuthorizationRequest(ctx context.Context, req *CreatePushedAuthorizationRequestRequest) (*CreatePushedAuthorizationRequestResponse, error)
// GetPushedAuthorizationRequest returns the record for a request_uri
// or ErrPushedRequestNotFound. It does NOT filter by expiry or
// consumption: the authorization endpoint checks both so it can tell
// a client that its reference expired rather than that it never
// existed.
GetPushedAuthorizationRequest(ctx context.Context, req *GetPushedAuthorizationRequestRequest) (*GetPushedAuthorizationRequestResponse, error)
// ConsumePushedAuthorizationRequest marks a record redeemed. Called
// before an authorization code is issued against it; the response's
// Consumed is true only for the call that marked it, which makes it
// the single-use gate. Returns ErrPushedRequestNotFound when no record
// matches.
//
// Marking rather than deleting is deliberate: a client that replays a
// consumed reference should be told its request was already used,
// which a deleted record cannot distinguish from one that never
// existed. CleanupExpired reclaims the row afterwards.
ConsumePushedAuthorizationRequest(ctx context.Context, req *ConsumePushedAuthorizationRequestRequest) (*ConsumePushedAuthorizationRequestResponse, error)
// DeletePushedAuthorizationRequest removes a record. For callers
// tearing down an abandoned flow. Returns ErrPushedRequestNotFound
// when no record matches.
DeletePushedAuthorizationRequest(ctx context.Context, req *DeletePushedAuthorizationRequestRequest) (*DeletePushedAuthorizationRequestResponse, error)
// CleanupExpiredPushedRequests drops every record past its expiry,
// consumed or not. Production deployments run this on a timer; tests
// call it directly.
CleanupExpiredPushedRequests(ctx context.Context, req *CleanupExpiredPushedRequestsRequest) (*CleanupExpiredPushedRequestsResponse, error)
}
PushedAuthorizationRequestStore persists RFC 9126 pushed authorization requests between the back-channel push and the browser-borne authorization request that references them.
Records are short-lived by design. §2.2 puts a typical request_uri lifetime between 5 and 600 seconds, so a backend does not need to be durable across long outages, but it does need to be shared by every node that serves /authorize: a reference pushed to one node and dereferenced on another must resolve, which is what an in-process store cannot do.
The store does not enforce single-use. Consume records the fact; the authorization endpoint decides when to call it, because only that handler knows whether a code was actually issued.
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 RecordAuthorizationCodeTokensRequest ¶ added in v0.1.57
type RecordAuthorizationCodeTokensRequest struct {
Code string
TokenID string
TokenExpiresAt time.Time
RefreshFamily string
}
RecordAuthorizationCodeTokensRequest names the tokens a redemption minted from Code.
type RecordAuthorizationCodeTokensResponse ¶ added in v0.1.57
type RecordAuthorizationCodeTokensResponse struct {
ReuseDetected bool
}
RecordAuthorizationCodeTokensResponse reports whether the code was presented again before the tokens were recorded.
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
Confirmation *Confirmation `json:"confirmation,omitempty"` // RFC 9449 §5 sender-constraint; nil = plain bearer
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.
CreateRefreshToken(ctx context.Context, req *CreateRefreshTokenRequest) (*CreateRefreshTokenResponse, error)
// GetRefreshToken retrieves a refresh token by its value.
GetRefreshToken(ctx context.Context, req *GetRefreshTokenRequest) (*GetRefreshTokenResponse, error)
// RotateRefreshToken invalidates old token and creates new one in same family.
// Returns ErrTokenReused if the old token was already revoked (theft detection),
// including when a concurrent call rotated it first: only one caller may
// get a new token. The new token keeps the old one's ExpiresAt, so a family
// lives at most TokenExpiryRefreshToken from its first issue however often
// it is refreshed (RFC 9700 §4.14.2).
RotateRefreshToken(ctx context.Context, req *RotateRefreshTokenRequest) (*RotateRefreshTokenResponse, error)
// RevokeRefreshToken marks a token as revoked.
RevokeRefreshToken(ctx context.Context, req *RevokeRefreshTokenRequest) (*RevokeRefreshTokenResponse, error)
// RevokeSubjectTokens revokes all refresh tokens belonging to a subject.
RevokeSubjectTokens(ctx context.Context, req *RevokeSubjectTokensRequest) (*RevokeSubjectTokensResponse, error)
// RevokeTokenFamily revokes all tokens in a family (theft detection).
RevokeTokenFamily(ctx context.Context, req *RevokeTokenFamilyRequest) (*RevokeTokenFamilyResponse, error)
// GetSubjectTokens lists all active (non-revoked, non-expired) refresh
// tokens for a subject.
GetSubjectTokens(ctx context.Context, req *GetSubjectTokensRequest) (*GetSubjectTokensResponse, error)
// CleanupExpiredTokens removes expired tokens (for maintenance).
CleanupExpiredTokens(ctx context.Context, req *CleanupExpiredTokensRequest) (*CleanupExpiredTokensResponse, error)
}
RefreshTokenStore manages refresh tokens for API access.
type RevokeAPIKeyRequest ¶ added in v0.1.7
type RevokeAPIKeyRequest struct {
KeyID string
}
type RevokeAPIKeyResponse ¶ added in v0.1.7
type RevokeAPIKeyResponse struct{}
type RevokeRefreshTokenRequest ¶ added in v0.1.7
type RevokeRefreshTokenRequest struct {
Token string
}
type RevokeRefreshTokenResponse ¶ added in v0.1.7
type RevokeRefreshTokenResponse struct{}
type RevokeSubjectTokensRequest ¶ added in v0.1.7
type RevokeSubjectTokensRequest struct {
Subject string
}
type RevokeSubjectTokensResponse ¶ added in v0.1.7
type RevokeSubjectTokensResponse struct{}
type RevokeTokenFamilyRequest ¶ added in v0.1.7
type RevokeTokenFamilyRequest struct {
Family string
}
type RevokeTokenFamilyResponse ¶ added in v0.1.7
type RevokeTokenFamilyResponse struct{}
type RotateRefreshTokenRequest ¶ added in v0.1.7
type RotateRefreshTokenRequest struct {
OldToken string
}
type RotateRefreshTokenResponse ¶ added in v0.1.7
type RotateRefreshTokenResponse struct {
Token *RefreshToken
}
type SaveAppRequest ¶ added in v0.1.9
type SaveAppRequest struct {
App *AppRegistration
}
SaveAppRequest carries the registration to persist.
type SaveAppResponse ¶ added in v0.1.9
type SaveAppResponse struct{}
SaveAppResponse is empty; SaveApp returns only an error signal.
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", or "DPoP" when the token is sender-constrained (RFC 9449 §5)
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"`
// DeviceCode is the high-entropy identifier returned by
// /device/authorize (RFC 8628 §3.2) that the device polls with at
// grant_type=urn:ietf:params:oauth:grant-type:device_code.
DeviceCode string `json:"device_code,omitempty"`
// Code is the high-entropy authorization code returned to the
// client via the /authorize redirect (RFC 6749 §4.1.2). Sent on
// grant_type=authorization_code along with CodeVerifier and
// RedirectURI.
Code string `json:"code,omitempty"`
// CodeVerifier is the PKCE verifier (RFC 7636 §4.1) the client
// committed to via code_challenge at /authorize time. Required on
// grant_type=authorization_code when the original authorization
// request included a code_challenge.
CodeVerifier string `json:"code_verifier,omitempty"`
// RedirectURI is the verbatim redirect_uri the client used at the
// /authorize step. RFC 6749 §4.1.3 requires it on the
// authorization_code redemption so the AS can re-check binding.
RedirectURI string `json:"redirect_uri,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 TrustedProxies ¶ added in v0.1.45
TrustedProxies lists the addresses whose X-Forwarded-For / X-Real-IP headers are believed: the reverse proxies or load balancers in front of this process. Empty means none, so the client address is always the TCP peer.
func ParseTrustedProxies ¶ added in v0.1.45
func ParseTrustedProxies(entries []string) (TrustedProxies, error)
ParseTrustedProxies parses CIDRs ("10.0.0.0/8") and single addresses ("192.0.2.10") into TrustedProxies.
type UpdateAPIKeyLastUsedRequest ¶ added in v0.1.7
type UpdateAPIKeyLastUsedRequest struct {
KeyID string
}
type UpdateAPIKeyLastUsedResponse ¶ added in v0.1.7
type UpdateAPIKeyLastUsedResponse struct{}
type UpdatePollingStateRequest ¶ added in v0.1.23
UpdatePollingStateRequest records that a device just polled. The store updates LastPolledAt and, when SlowDown is true, raises IntervalSeconds by 5 (RFC 8628 §3.5).
type UpdatePollingStateResponse ¶ added in v0.1.23
type UpdatePollingStateResponse struct {
Authorization *DeviceAuthorization
}
UpdatePollingStateResponse wraps the updated authorization.
type ValidateAPIKeyRequest ¶ added in v0.1.7
type ValidateAPIKeyRequest struct {
FullKey string
}
type ValidateAPIKeyResponse ¶ added in v0.1.7
type ValidateAPIKeyResponse struct {
APIKey *APIKey
}