bff

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Mar 23, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package bff implements the Backend for Frontend (BFF) pattern for secure session management. The BFF holds tokens server-side and uses HTTP-only cookies to identify browser sessions.

Index

Constants

View Source
const (
	// ErrCodeNoSession indicates no session cookie was provided.
	ErrCodeNoSession = "no_session"
	// ErrCodeSessionExpired indicates the session has expired.
	ErrCodeSessionExpired = "session_expired"
	// ErrCodeInvalidSession indicates the session is invalid.
	ErrCodeInvalidSession = "invalid_session"
	// ErrCodeCSRFViolation indicates CSRF protection blocked the request.
	ErrCodeCSRFViolation = "csrf_violation"
	// ErrCodeRateLimited indicates rate limit was exceeded.
	ErrCodeRateLimited = "rate_limited"
)

Common error codes for documentation

View Source
const (
	// ContextKeySession is the context key for the session.
	ContextKeySession contextKey = "bff_session"
)
View Source
const SessionIDLength = 32

SessionIDLength is the length of generated session IDs in bytes.

Variables

View Source
var (
	// ErrSessionNotFound is returned when a session is not found.
	ErrSessionNotFound = errors.New("session not found")
	// ErrSessionExpired is returned when a session has expired.
	ErrSessionExpired = errors.New("session expired")
	// ErrInvalidSession is returned when a session is invalid.
	ErrInvalidSession = errors.New("invalid session")
	// ErrStoreRequired is returned when a store is not provided.
	ErrStoreRequired = errors.New("session store is required")
	// ErrOriginsRequired is returned when no allowed origins are provided.
	ErrOriginsRequired = errors.New("allowed origins are required for CSRF protection")
)
View Source
var ErrNoDPoPKeyPair = errors.New("session has no DPoP key pair")

ErrNoDPoPKeyPair is returned when the session has no DPoP key pair.

View Source
var ErrRefreshFailed = errors.New("token refresh failed")

ErrRefreshFailed is returned when token refresh fails.

View Source
var ErrRefreshTokenExpired = errors.New("refresh token expired")

ErrRefreshTokenExpired is returned when the refresh token has expired.

View Source
var ErrTokenEndpointRequired = errors.New("token endpoint required")

ErrTokenEndpointRequired is returned when the token endpoint is not configured.

Functions

func APIProxyMiddleware

func APIProxyMiddleware(targetURL string) (func(http.Handler) http.Handler, error)

APIProxyMiddleware creates middleware that proxies all requests to an API backend. This is useful for wrapping the proxy handler with session middleware.

func AddBFFSecurityScheme added in v0.4.0

func AddBFFSecurityScheme(api huma.API, cookieName string)

AddBFFSecurityScheme adds the BFF cookie-based security scheme to the OpenAPI spec.

func AutoRefreshMiddleware

func AutoRefreshMiddleware(config RefreshConfig) (func(http.Handler) http.Handler, error)

AutoRefreshMiddleware creates middleware that automatically refreshes tokens before expiry.

func GenerateSessionID

func GenerateSessionID() (string, error)

GenerateSessionID generates a cryptographically secure session ID.

func GetCloudflareMetadata added in v0.4.0

func GetCloudflareMetadata(r *http.Request) map[string]string

GetCloudflareMetadata extracts Cloudflare-specific metadata from the request.

func OptionalSessionMiddleware

func OptionalSessionMiddleware(store Store, cookieManager *CookieManager) func(http.Handler) http.Handler

OptionalSessionMiddleware creates middleware that loads sessions if present. Requests without sessions are allowed through.

func OriginMiddleware

func OriginMiddleware(allowedOrigins ...string) func(http.Handler) http.Handler

OriginMiddleware creates origin validation middleware with the given allowed origins. This is a convenience function for simple use cases.

func RateLimitMiddleware added in v0.4.0

func RateLimitMiddleware(requestsPerMinute, burstSize int) func(http.Handler) http.Handler

RateLimitMiddleware creates rate limiting middleware with default config.

func RefreshHandler

func RefreshHandler(config RefreshConfig) http.Handler

RefreshHandler returns an HTTP handler for explicit token refresh requests. This can be used by the frontend to proactively refresh tokens.

func RegisterHumaRoutes added in v0.4.0

func RegisterHumaRoutes(api huma.API, config HumaConfig)

RegisterHumaRoutes registers BFF endpoints with a Huma API for OpenAPI generation. The actual request handling is done by chi-based handlers in Handler.Router().

Usage:

// Create BFF handler
bffHandler, _ := bff.NewHandler(config)

// Mount chi routes for actual handling
router.Mount("/bff", bffHandler.Router())

// Register with Huma for OpenAPI spec
bff.RegisterHumaRoutes(humaAPI, bff.HumaConfig{
    Handler:    bffHandler,
    PathPrefix: "/bff",
})

func RequireSessionMiddleware

func RequireSessionMiddleware(store Store, cookieManager *CookieManager) func(http.Handler) http.Handler

RequireSessionMiddleware creates middleware that requires a valid session. This is a convenience function that wraps SessionMiddleware.

func SessionMiddleware

func SessionMiddleware(config MiddlewareConfig) func(http.Handler) http.Handler

SessionMiddleware creates middleware that loads sessions from cookies.

func SimpleProxy

func SimpleProxy(targetURL string) (http.Handler, error)

SimpleProxy creates a simple proxy handler with default configuration.

Types

type BFFErrorResponse added in v0.4.0

type BFFErrorResponse struct {
	// Error is the error code.
	Error string `json:"error" doc:"Error code" example:"session_expired"`

	// Message is the human-readable error message.
	Message string `json:"message" doc:"Human-readable error message" example:"Your session has expired"`
}

BFFErrorResponse is returned for BFF-specific errors.

type ClientIPConfig added in v0.4.0

type ClientIPConfig struct {
	// TrustCloudflare enables Cloudflare header support.
	// When true, checks CF-Connecting-IP and True-Client-IP headers.
	// Default: false.
	TrustCloudflare bool

	// TrustProxy enables standard proxy headers (X-Forwarded-For, X-Real-IP).
	// Only enable if behind a trusted reverse proxy.
	// Default: false.
	TrustProxy bool

	// TrustedProxies is a list of trusted proxy IP ranges.
	// If set, proxy headers are only trusted when the request comes from these IPs.
	// Supports CIDR notation (e.g., "10.0.0.0/8", "172.16.0.0/12").
	TrustedProxies []string

	// CloudflareIPRanges can be set to validate that CF headers come from Cloudflare.
	// If empty and TrustCloudflare is true, CF headers are trusted unconditionally.
	// Cloudflare publishes their IP ranges at:
	// https://www.cloudflare.com/ips-v4 and https://www.cloudflare.com/ips-v6
	CloudflareIPRanges []string
	// contains filtered or unexported fields
}

ClientIPConfig contains configuration for client IP extraction.

func CloudflareClientIPConfig added in v0.4.0

func CloudflareClientIPConfig() ClientIPConfig

CloudflareClientIPConfig returns configuration for Cloudflare deployments.

func DefaultClientIPConfig added in v0.4.0

func DefaultClientIPConfig() ClientIPConfig

DefaultClientIPConfig returns a safe default configuration. By default, no proxy headers are trusted.

type ClientIPExtractor added in v0.4.0

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

ClientIPExtractor extracts the real client IP from requests.

func NewClientIPExtractor added in v0.4.0

func NewClientIPExtractor(config ClientIPConfig) *ClientIPExtractor

NewClientIPExtractor creates a new client IP extractor.

func (*ClientIPExtractor) GetClientIP added in v0.4.0

func (e *ClientIPExtractor) GetClientIP(r *http.Request) string

GetClientIP extracts the real client IP from the request. It checks headers in order of trust: Cloudflare > Proxy > RemoteAddr.

type CookieConfig

type CookieConfig struct {
	// Name is the cookie name. Default: "cf_session".
	Name string

	// Domain is the cookie domain. If empty, uses the request host.
	Domain string

	// Path is the cookie path. Default: "/".
	Path string

	// MaxAge is the cookie max age in seconds. Default: 0 (session cookie).
	// Set to -1 to delete the cookie.
	MaxAge int

	// Secure indicates the cookie should only be sent over HTTPS.
	// Default: true.
	Secure bool

	// HTTPOnly prevents JavaScript access to the cookie.
	// Default: true (required for security).
	HTTPOnly bool

	// SameSite controls cross-site cookie behavior.
	// Default: SameSiteStrictMode.
	SameSite http.SameSite
}

CookieConfig contains configuration for session cookies.

func DefaultCookieConfig

func DefaultCookieConfig() CookieConfig

DefaultCookieConfig returns secure default cookie configuration.

type CookieManager

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

CookieManager handles session cookie operations.

func NewCookieManager

func NewCookieManager(config CookieConfig) *CookieManager

NewCookieManager creates a new cookie manager with the given configuration.

func (*CookieManager) ClearSessionCookie

func (m *CookieManager) ClearSessionCookie(w http.ResponseWriter)

ClearSessionCookie removes the session cookie from the response.

func (*CookieManager) Config

func (m *CookieManager) Config() CookieConfig

Config returns the cookie configuration.

func (*CookieManager) GetSessionID

func (m *CookieManager) GetSessionID(r *http.Request) string

GetSessionID extracts the session ID from the request cookie. Returns empty string if the cookie is not present.

func (*CookieManager) SetSessionCookie

func (m *CookieManager) SetSessionCookie(w http.ResponseWriter, sessionID string, expiry time.Time)

SetSessionCookie creates and sets a session cookie on the response.

type CreateSessionParams added in v0.4.0

type CreateSessionParams struct {
	UserID                uuid.UUID
	OrganizationID        *uuid.UUID
	AccessToken           string
	RefreshToken          string
	AccessTokenExpiresIn  time.Duration
	RefreshTokenExpiresIn time.Duration
	DPoPKeyPairJSON       []byte // Serialized DPoP key pair (from dpop.KeyPair.SerializeJSON())
	DPoPThumbprint        string // JWK thumbprint of the DPoP key pair
	Metadata              map[string]string
}

CreateSessionParams contains parameters for creating a session.

type EndpointLimit added in v0.4.0

type EndpointLimit struct {
	RequestsPerMinute int
	BurstSize         int
}

EndpointLimit defines rate limits for a specific endpoint.

type GetSessionInput added in v0.4.0

type GetSessionInput struct{}

GetSessionInput is the input for getting session status.

type GetSessionOutput added in v0.4.0

type GetSessionOutput struct {
	Body SessionStatusResponse
}

GetSessionOutput is the response for session status.

type Handler added in v0.4.0

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

Handler provides BFF endpoints with built-in security.

func NewHandler added in v0.4.0

func NewHandler(config HandlerConfig) (*Handler, error)

NewHandler creates a new BFF handler.

func (*Handler) Close added in v0.4.0

func (h *Handler) Close() error

Close closes the handler and releases resources.

func (*Handler) CookieManager added in v0.4.0

func (h *Handler) CookieManager() *CookieManager

CookieManager returns the cookie manager.

func (*Handler) CreateSession added in v0.4.0

func (h *Handler) CreateSession(ctx context.Context, w http.ResponseWriter, r *http.Request, params CreateSessionParams) (*Session, error)

CreateSession creates a new BFF session and sets the cookie. This is typically called after OAuth callback completes.

func (*Handler) Router added in v0.4.0

func (h *Handler) Router() chi.Router

Router returns a chi router with all BFF routes.

func (*Handler) Store added in v0.4.0

func (h *Handler) Store() Store

Store returns the session store.

type HandlerConfig added in v0.4.0

type HandlerConfig struct {
	// Store is the session store. Required.
	Store Store

	// CookieConfig configures session cookies.
	// If zero value, uses DefaultCookieConfig().
	CookieConfig CookieConfig

	// ProxyConfig configures the API proxy.
	// TargetURL is required if using the proxy.
	ProxyConfig ProxyConfig

	// AllowedOrigins is a list of allowed origins for CSRF protection.
	// Required for security. Example: ["https://myapp.com", "https://app.myapp.com"]
	AllowedOrigins []string

	// ClientIPConfig configures client IP extraction.
	// If zero value, uses DefaultClientIPConfig().
	ClientIPConfig ClientIPConfig

	// SessionIDGenerator generates session IDs.
	// If nil, uses GenerateSessionID().
	SessionIDGenerator func() (string, error)

	// OnCreateSession is called when a session is created.
	// Can be used to persist refresh tokens to a database.
	OnCreateSession func(ctx context.Context, session *Session) error

	// OnRefresh is called to refresh tokens.
	// Must return new access token, optional new refresh token, and expiry.
	// If nil, the /refresh endpoint returns 501 Not Implemented.
	OnRefresh func(ctx context.Context, session *Session) (*TokenRefreshResult, error)

	// OnLogout is called when a session is logged out.
	// Can be used to revoke refresh tokens in the database.
	OnLogout func(ctx context.Context, session *Session) error

	// OnSessionLoad is called after a session is loaded from the store.
	// Can be used for logging or enriching session data.
	OnSessionLoad func(ctx context.Context, session *Session) error

	// BasePath is the base path for BFF routes.
	// Default: "" (routes at root of the mounted router).
	BasePath string

	// APIPathPrefix is the path prefix for proxied API requests.
	// Default: "/api".
	APIPathPrefix string

	// EnableProxyForPublicRoutes allows unauthenticated proxy requests.
	// When true, /api/* routes work without a session (for public APIs).
	// Default: false.
	EnableProxyForPublicRoutes bool

	// RateLimitConfig configures rate limiting for BFF endpoints.
	// If nil, rate limiting is disabled.
	RateLimitConfig *RateLimitConfig
}

HandlerConfig contains configuration for the BFF handler.

type HumaConfig added in v0.4.0

type HumaConfig struct {
	// Handler is the BFF handler to use for operations.
	Handler *Handler

	// PathPrefix is the prefix for all BFF routes.
	// Default: "/bff".
	PathPrefix string

	// Tags are OpenAPI tags for BFF endpoints.
	// Default: ["BFF"].
	Tags []string

	// IncludeRateLimitDocs adds x-ratelimit-* extensions to OpenAPI.
	// Default: true.
	IncludeRateLimitDocs bool
}

HumaConfig contains configuration for Huma BFF registration.

type LogoutInput added in v0.4.0

type LogoutInput struct {
	Origin string `header:"Origin" required:"true" doc:"Origin header for CSRF protection"`
}

LogoutInput is the input for logout.

type LogoutOutput added in v0.4.0

type LogoutOutput struct {
	Body LogoutResponse
}

LogoutOutput is the response for logout.

type LogoutResponse added in v0.4.0

type LogoutResponse struct {
	// Message indicates the logout result.
	Message string `json:"message" doc:"Logout result message" example:"Logged out successfully"`
}

LogoutResponse contains the logout result.

type MemoryStore

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

MemoryStore is an in-memory session store for development and testing. It is thread-safe and supports automatic cleanup of expired sessions.

func NewMemoryStore

func NewMemoryStore(config StoreConfig) *MemoryStore

NewMemoryStore creates a new in-memory session store.

func (*MemoryStore) Cleanup

func (s *MemoryStore) Cleanup(ctx context.Context) (int, error)

Cleanup removes expired sessions.

func (*MemoryStore) Close

func (s *MemoryStore) Close() error

Close stops the cleanup goroutine and releases resources.

func (*MemoryStore) Count

func (s *MemoryStore) Count() int

Count returns the current number of sessions (for testing).

func (*MemoryStore) Create

func (s *MemoryStore) Create(ctx context.Context, session *Session) error

Create stores a new session.

func (*MemoryStore) Delete

func (s *MemoryStore) Delete(ctx context.Context, id string) error

Delete removes a session by ID.

func (*MemoryStore) DeleteByUserID

func (s *MemoryStore) DeleteByUserID(ctx context.Context, userID string) (int, error)

DeleteByUserID removes all sessions for a user.

func (*MemoryStore) Get

func (s *MemoryStore) Get(ctx context.Context, id string) (*Session, error)

Get retrieves a session by ID.

func (*MemoryStore) Touch

func (s *MemoryStore) Touch(ctx context.Context, id string) error

Touch updates the LastAccessedAt timestamp.

func (*MemoryStore) Update

func (s *MemoryStore) Update(ctx context.Context, session *Session) error

Update updates an existing session.

type MiddlewareConfig

type MiddlewareConfig struct {
	// Store is the session store.
	Store Store

	// CookieManager handles session cookies.
	CookieManager *CookieManager

	// RefreshThreshold is how long before access token expiry to trigger refresh.
	// Default: 5 minutes.
	RefreshThreshold time.Duration

	// OnSessionLoad is called after a session is loaded.
	// Can be used for logging or session modification.
	OnSessionLoad func(ctx context.Context, session *Session) error

	// OnSessionExpired is called when a session is expired.
	OnSessionExpired func(w http.ResponseWriter, r *http.Request)

	// OnSessionInvalid is called when a session is invalid.
	OnSessionInvalid func(w http.ResponseWriter, r *http.Request)

	// OnNoSession is called when no session is found and RequireSession is true.
	OnNoSession func(w http.ResponseWriter, r *http.Request)

	// RequireSession when true rejects requests without valid sessions.
	RequireSession bool

	// TouchOnAccess when true updates LastAccessedAt on each request.
	TouchOnAccess bool
}

MiddlewareConfig contains configuration for the BFF middleware.

type OriginConfig

type OriginConfig struct {
	// AllowedOrigins is a list of allowed origins.
	// Origins should be in the format "https://example.com" (no trailing slash).
	AllowedOrigins []string

	// AllowedHosts is a list of allowed hosts (without scheme).
	// This is an alternative to AllowedOrigins for simpler configuration.
	AllowedHosts []string

	// OnError is called when origin validation fails.
	// If nil, returns 403 Forbidden.
	OnError func(w http.ResponseWriter, r *http.Request)

	// AllowMissingOrigin allows requests without Origin header.
	// Default: false (more secure).
	AllowMissingOrigin bool

	// CheckReferer uses Referer header as fallback when Origin is missing.
	// Default: true.
	CheckReferer bool

	// SkipMethods is a list of HTTP methods to skip origin validation.
	// Typically safe methods (GET, HEAD, OPTIONS) can be skipped.
	// Default: none (validate all methods).
	SkipMethods []string
}

OriginConfig contains configuration for origin validation.

func DefaultOriginConfig

func DefaultOriginConfig() OriginConfig

DefaultOriginConfig returns default origin validation configuration.

type OriginValidator

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

OriginValidator validates request origins.

func NewOriginValidator

func NewOriginValidator(config OriginConfig) *OriginValidator

NewOriginValidator creates a new origin validator.

func (*OriginValidator) Middleware

func (v *OriginValidator) Middleware() func(http.Handler) http.Handler

Middleware returns HTTP middleware that validates request origins.

func (*OriginValidator) ValidateRequest

func (v *OriginValidator) ValidateRequest(r *http.Request) bool

ValidateRequest validates the request origin.

type Proxy

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

Proxy proxies requests to an API backend with session-based authentication.

func NewProxy

func NewProxy(config ProxyConfig) (*Proxy, error)

NewProxy creates a new API proxy.

func (*Proxy) Handler

func (p *Proxy) Handler() http.Handler

Handler returns an HTTP handler that proxies requests.

func (*Proxy) ProxyRequest

func (p *Proxy) ProxyRequest(ctx context.Context, method, path string, body io.Reader) (*http.Response, error)

ProxyRequest proxies a single request (for non-streaming use cases).

type ProxyConfig

type ProxyConfig struct {
	// TargetURL is the base URL of the API backend.
	TargetURL string

	// UseDPoP enables DPoP proof injection.
	// Default: true
	UseDPoP bool

	// Client is the HTTP client to use for proxied requests.
	// If nil, uses http.DefaultClient.
	Client *http.Client

	// Timeout is the request timeout.
	// Default: 30 seconds.
	Timeout time.Duration

	// OnError is called when a proxy error occurs.
	// If nil, returns 502 Bad Gateway.
	OnError func(w http.ResponseWriter, r *http.Request, err error)

	// OnRequestRewrite allows modifying the proxied request before sending.
	OnRequestRewrite func(r *http.Request, session *Session)

	// PathRewrite allows modifying the request path.
	// The function receives the original path and returns the rewritten path.
	PathRewrite func(path string) string

	// StripPrefix removes a prefix from the request path before proxying.
	StripPrefix string

	// HeadersToForward specifies which request headers to forward.
	// If empty, forwards all headers except hop-by-hop headers.
	HeadersToForward []string

	// HeadersToRemove specifies headers to remove from the proxied request.
	HeadersToRemove []string

	// ResponseHeadersToRemove specifies headers to remove from the response.
	ResponseHeadersToRemove []string
}

ProxyConfig contains configuration for the API proxy.

func DefaultProxyConfig

func DefaultProxyConfig() ProxyConfig

DefaultProxyConfig returns default proxy configuration.

type RateLimitConfig added in v0.4.0

type RateLimitConfig struct {
	// RequestsPerMinute is the sustained request rate.
	// Default: 60.
	RequestsPerMinute int

	// BurstSize is the maximum burst of requests allowed.
	// Default: 10.
	BurstSize int

	// EndpointLimits allows per-endpoint rate limit overrides.
	// Key is the endpoint path (e.g., "/refresh", "/logout").
	EndpointLimits map[string]EndpointLimit

	// KeyFunc extracts the rate limit key from a request.
	// Default: client IP address.
	KeyFunc func(r *http.Request) string

	// OnLimitExceeded is called when rate limit is exceeded.
	// If nil, returns 429 Too Many Requests with standard headers.
	OnLimitExceeded func(w http.ResponseWriter, r *http.Request, info RateLimitInfo)

	// ExcludePaths are paths that bypass rate limiting.
	// Example: []string{"/health", "/metrics"}
	ExcludePaths []string

	// TrustCloudflare uses CF-Connecting-IP for client identification.
	// Default: false.
	TrustCloudflare bool

	// CleanupInterval is how often to clean up expired entries.
	// Default: 1 minute.
	CleanupInterval time.Duration
}

RateLimitConfig contains configuration for rate limiting.

func DefaultRateLimitConfig added in v0.4.0

func DefaultRateLimitConfig() RateLimitConfig

DefaultRateLimitConfig returns sensible default rate limit configuration.

type RateLimitInfo added in v0.4.0

type RateLimitInfo struct {
	Limit      int       // Maximum requests per window
	Remaining  int       // Requests remaining in current window
	ResetAt    time.Time // When the window resets
	RetryAfter int       // Seconds until next request allowed (if limited)
}

RateLimitInfo contains information about the current rate limit state.

type RateLimiter added in v0.4.0

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

RateLimiter implements token bucket rate limiting.

func NewRateLimiter added in v0.4.0

func NewRateLimiter(config RateLimitConfig) *RateLimiter

NewRateLimiter creates a new rate limiter.

func (*RateLimiter) Close added in v0.4.0

func (rl *RateLimiter) Close()

Close stops the cleanup goroutine.

func (*RateLimiter) Middleware added in v0.4.0

func (rl *RateLimiter) Middleware() func(http.Handler) http.Handler

Middleware returns HTTP middleware that enforces rate limits.

type RefreshConfig

type RefreshConfig struct {
	// Store is the session store.
	Store Store

	// CookieManager handles session cookies.
	CookieManager *CookieManager

	// TokenEndpoint is the OAuth token endpoint URL.
	TokenEndpoint string

	// ClientID is the OAuth client ID.
	ClientID string

	// ClientSecret is the OAuth client secret (optional for public clients).
	ClientSecret string //nolint:gosec // G117: config field, not a hardcoded secret

	// UseDPoP enables DPoP for token refresh requests.
	// When true, generates a new DPoP key pair and binds the new tokens to it.
	// Default: true.
	UseDPoP bool

	// Client is the HTTP client to use for refresh requests.
	// If nil, uses http.DefaultClient.
	Client *http.Client

	// Timeout is the refresh request timeout.
	// Default: 30 seconds.
	Timeout time.Duration

	// RefreshThreshold is how early before expiry to refresh tokens.
	// Default: 5 minutes.
	RefreshThreshold time.Duration

	// OnRefreshSuccess is called when tokens are successfully refreshed.
	OnRefreshSuccess func(ctx context.Context, session *Session)

	// OnRefreshError is called when token refresh fails.
	OnRefreshError func(w http.ResponseWriter, r *http.Request, err error)

	// ParseTokenResponse allows custom parsing of the token response.
	// If nil, uses the default OAuth2 token response format.
	ParseTokenResponse func(body []byte) (*TokenResponse, error)
}

RefreshConfig contains configuration for the token refresh handler.

func DefaultRefreshConfig

func DefaultRefreshConfig() RefreshConfig

DefaultRefreshConfig returns default refresh configuration.

type RefreshInput added in v0.4.0

type RefreshInput struct {
	Origin string `header:"Origin" required:"true" doc:"Origin header for CSRF protection"`
}

RefreshInput is the input for token refresh.

type RefreshOutput added in v0.4.0

type RefreshOutput struct {
	Body RefreshResponse

	// Standard rate limit headers
	RateLimitLimit     int   `header:"X-RateLimit-Limit" doc:"Maximum requests per window"`
	RateLimitRemaining int   `header:"X-RateLimit-Remaining" doc:"Requests remaining in current window"`
	RateLimitReset     int64 `header:"X-RateLimit-Reset" doc:"Unix timestamp when window resets"`
}

RefreshOutput is the response for token refresh.

type RefreshResponse added in v0.4.0

type RefreshResponse struct {
	// Message indicates the refresh result.
	Message string `json:"message" doc:"Refresh result message" example:"Session refreshed"`

	// ExpiresAt is the Unix timestamp when the new access token expires.
	ExpiresAt int64 `json:"expires_at" doc:"Unix timestamp when access token expires"`
}

RefreshResponse contains the refresh result.

type Refresher

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

Refresher handles automatic token refresh.

func NewRefresher

func NewRefresher(config RefreshConfig) (*Refresher, error)

NewRefresher creates a new token refresher.

func (*Refresher) Middleware

func (r *Refresher) Middleware() func(http.Handler) http.Handler

Middleware returns HTTP middleware that automatically refreshes expired access tokens.

func (*Refresher) RefreshSession

func (r *Refresher) RefreshSession(ctx context.Context, session *Session) error

RefreshSession refreshes the tokens for a session.

type Session

type Session struct {
	// ID is the unique session identifier.
	ID string `json:"id"`

	// UserID is the authenticated user's ID.
	UserID uuid.UUID `json:"user_id"`

	// OrganizationID is the current organization context (optional).
	OrganizationID *uuid.UUID `json:"organization_id,omitempty"`

	// AccessToken is the OAuth access token.
	AccessToken string `json:"access_token"`

	// RefreshToken is the OAuth refresh token.
	RefreshToken string `json:"refresh_token"`

	// AccessTokenExpiresAt is when the access token expires.
	AccessTokenExpiresAt time.Time `json:"access_token_expires_at"`

	// RefreshTokenExpiresAt is when the refresh token expires.
	RefreshTokenExpiresAt time.Time `json:"refresh_token_expires_at"`

	// DPoPKeyPairJSON is the serialized DPoP key pair for this session.
	// The BFF uses this to sign DPoP proofs when proxying requests to the API.
	DPoPKeyPairJSON []byte `json:"dpop_key_pair,omitempty"`

	// DPoPThumbprint is the JWK thumbprint of the DPoP key pair.
	DPoPThumbprint string `json:"dpop_thumbprint,omitempty"`

	// Metadata contains optional session metadata.
	Metadata map[string]string `json:"metadata,omitempty"`

	// CreatedAt is when the session was created.
	CreatedAt time.Time `json:"created_at"`

	// UpdatedAt is when the session was last updated.
	UpdatedAt time.Time `json:"updated_at"`

	// LastAccessedAt is when the session was last accessed.
	LastAccessedAt time.Time `json:"last_accessed_at"`

	// ExpiresAt is when the session expires (based on refresh token or absolute timeout).
	ExpiresAt time.Time `json:"expires_at"`

	// IPAddress is the IP address that created the session.
	IPAddress string `json:"ip_address,omitempty"`

	// UserAgent is the user agent that created the session.
	UserAgent string `json:"user_agent,omitempty"`
}

Session represents a server-side session with stored tokens and DPoP keys.

func GetSession

func GetSession(ctx context.Context) *Session

GetSession retrieves the session from the request context.

func NewSession

func NewSession(userID uuid.UUID, accessToken, refreshToken string, accessExpiry, refreshExpiry time.Duration) (*Session, error)

NewSession creates a new session with the given parameters.

func (*Session) GetDPoPKeyPair

func (s *Session) GetDPoPKeyPair() (*dpop.KeyPair, error)

GetDPoPKeyPair deserializes and returns the session's DPoP key pair.

func (*Session) HasDPoP

func (s *Session) HasDPoP() bool

HasDPoP returns true if the session has a DPoP key pair.

func (*Session) IsAccessTokenExpired

func (s *Session) IsAccessTokenExpired() bool

IsAccessTokenExpired returns true if the access token has expired.

func (*Session) IsExpired

func (s *Session) IsExpired() bool

IsExpired returns true if the session has expired.

func (*Session) IsRefreshTokenExpired

func (s *Session) IsRefreshTokenExpired() bool

IsRefreshTokenExpired returns true if the refresh token has expired.

func (*Session) NeedsRefresh

func (s *Session) NeedsRefresh(threshold time.Duration) bool

NeedsRefresh returns true if the access token needs to be refreshed. This returns true if the access token is expired or will expire soon.

func (*Session) SetDPoPKeyPair

func (s *Session) SetDPoPKeyPair(kp *dpop.KeyPair) error

SetDPoPKeyPair stores the DPoP key pair in the session.

type SessionInfoResponse added in v0.4.0

type SessionInfoResponse struct {
	Authenticated bool       `json:"authenticated"`
	UserID        *uuid.UUID `json:"user_id,omitempty"`
	ExpiresAt     *time.Time `json:"expires_at,omitempty"`
}

SessionInfoResponse is the response for session info requests.

type SessionStatusResponse added in v0.4.0

type SessionStatusResponse struct {
	// Authenticated indicates if the user has a valid session.
	Authenticated bool `json:"authenticated" doc:"Whether the user has a valid session"`

	// UserID is the authenticated user's ID (only present if authenticated).
	UserID *uuid.UUID `json:"user_id,omitempty" doc:"The authenticated user's ID"`

	// OrganizationID is the current organization context (only present if set).
	OrganizationID *uuid.UUID `json:"organization_id,omitempty" doc:"Current organization context"`

	// ExpiresAt is when the session expires (only present if authenticated).
	ExpiresAt *time.Time `json:"expires_at,omitempty" doc:"When the session expires" format:"date-time"`

	// AccessTokenExpiresAt is when the access token expires (only present if authenticated).
	AccessTokenExpiresAt *time.Time `json:"access_token_expires_at,omitempty" doc:"When the access token expires" format:"date-time"`
}

SessionStatusResponse contains session status information.

type Store

type Store interface {
	// Create stores a new session and returns the session ID.
	Create(ctx context.Context, session *Session) error

	// Get retrieves a session by ID.
	// Returns ErrSessionNotFound if the session doesn't exist.
	// Returns ErrSessionExpired if the session has expired.
	Get(ctx context.Context, id string) (*Session, error)

	// Update updates an existing session.
	// Returns ErrSessionNotFound if the session doesn't exist.
	Update(ctx context.Context, session *Session) error

	// Delete removes a session by ID.
	// Returns ErrSessionNotFound if the session doesn't exist.
	Delete(ctx context.Context, id string) error

	// DeleteByUserID removes all sessions for a user.
	// Returns the number of sessions deleted.
	DeleteByUserID(ctx context.Context, userID string) (int, error)

	// Touch updates the LastAccessedAt timestamp.
	// This is used to track session activity without modifying other fields.
	Touch(ctx context.Context, id string) error

	// Cleanup removes expired sessions.
	// Returns the number of sessions removed.
	Cleanup(ctx context.Context) (int, error)

	// Close closes the store and releases any resources.
	Close() error
}

Store defines the interface for session storage. Implementations must be safe for concurrent use.

type StoreConfig

type StoreConfig struct {
	// CleanupInterval is how often to run automatic cleanup.
	// Set to 0 to disable automatic cleanup.
	CleanupInterval int

	// MaxSessions is the maximum number of sessions to store (0 = unlimited).
	MaxSessions int

	// EncryptionKey is used to encrypt sensitive session data.
	// If nil, session data is stored unencrypted.
	EncryptionKey []byte
}

StoreConfig contains common configuration for session stores.

func DefaultStoreConfig

func DefaultStoreConfig() StoreConfig

DefaultStoreConfig returns sensible default configuration.

type TokenErrorResponse

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

TokenErrorResponse represents an OAuth2 error response.

type TokenRefreshResult added in v0.4.0

type TokenRefreshResult struct {
	AccessToken           string
	RefreshToken          string // Optional: new refresh token
	AccessTokenExpiresIn  time.Duration
	RefreshTokenExpiresIn time.Duration // Optional: if new refresh token issued
}

TokenRefreshResult contains the result of a token refresh operation.

type TokenResponse

type TokenResponse struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int    `json:"expires_in"`
	RefreshToken string `json:"refresh_token,omitempty"`
	Scope        string `json:"scope,omitempty"`
}

TokenResponse represents the OAuth2 token response.

Jump to

Keyboard shortcuts

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