middleware

package
v2.11.0 Latest Latest
Warning

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

Go to latest
Published: Apr 27, 2026 License: GPL-3.0 Imports: 19 Imported by: 0

Documentation

Index

Constants

View Source
const (
	RequestsPerMinute = 100 // Simple limit - 100 requests per minute per IP
	BurstSize         = 20  // Allow burst of 20 requests
)
View Source
const DefaultLastSeenFlushInterval = 30 * time.Second

DefaultLastSeenFlushInterval is how often the LastSeenTracker flushes pending updates to the database when StartFlushLoop is used without an explicit interval. 30s trades a small amount of database activity for a "last seen" estimate that is fresh enough to be useful in the paired clients list.

View Source
const EncryptionProtoVersion = 1

EncryptionProtoVersion is the current encryption protocol version. The value is included in the first WebSocket frame and rejected by the server if the client requests something unsupported.

Variables

View Source
var (
	ErrUnsupportedVersion    = errors.New("unsupported encryption version")
	ErrInvalidFrame          = errors.New("invalid encrypted frame")
	ErrInvalidSaltLength     = errors.New("session salt must be 16 bytes")
	ErrDuplicateSalt         = errors.New("duplicate session salt for client")
	ErrUnknownAuthToken      = errors.New("unknown auth token")
	ErrConnectionBlocked     = errors.New("connection blocked due to repeated failures")
	ErrInvalidPairingKey     = errors.New("stored pairing key is invalid")
	ErrSessionNotEstablished = errors.New("encryption session not established")
)

Encryption errors. These are returned by EncryptionGateway methods so callers can map them to appropriate WebSocket close codes or plaintext errors.

Functions

func HTTPAuthMiddleware added in v2.9.0

func HTTPAuthMiddleware(auth *AuthConfig) func(http.Handler) http.Handler

HTTPAuthMiddleware creates an HTTP middleware that validates API key authentication. If no keys are configured or the request is from localhost, all requests pass through. Returns 401 Unauthorized if keys are configured but no valid key is provided.

func HTTPRateLimitMiddleware

func HTTPRateLimitMiddleware(limiter *IPRateLimiter) func(http.Handler) http.Handler

HTTPRateLimitMiddleware creates an HTTP rate limiting middleware

func IsEncryptedFirstFrame added in v2.11.0

func IsEncryptedFirstFrame(data []byte) bool

IsEncryptedFirstFrame reports whether the given JSON bytes look like an encrypted first frame (has v + e + t + s populated). False if the bytes are not valid JSON or any required field is missing.

func IsLoopbackAddr added in v2.9.0

func IsLoopbackAddr(remoteAddr string) bool

IsLoopbackAddr checks if a RemoteAddr string represents a loopback address.

func NonWSIPFilterMiddleware added in v2.11.0

func NonWSIPFilterMiddleware(ipsProvider IPsProvider) func(http.Handler) http.Handler

NonWSIPFilterMiddleware denies non-loopback access unless AllowedIPs is configured.

func ParseRemoteIP added in v2.9.0

func ParseRemoteIP(remoteAddr string) net.IP

ParseRemoteIP extracts and parses the IP address from a RemoteAddr string (IP:port format).

func RunIPFilterMiddleware added in v2.11.0

func RunIPFilterMiddleware(ipsProvider IPsProvider, hasAllowRun func() bool) func(http.Handler) http.Handler

RunIPFilterMiddleware allows remote access to run endpoints when allow_run is configured (the handler validates content). Otherwise falls back to the standard AllowedIPs check.

func WebSocketAuthHandler added in v2.9.0

func WebSocketAuthHandler(auth *AuthConfig, r *http.Request) bool

WebSocketAuthHandler validates WebSocket connection requests. Returns true if the connection is allowed, false otherwise. If no keys are configured or the request is from localhost, all connections are allowed.

func WebSocketRateLimitHandler

func WebSocketRateLimitHandler(
	limiter *IPRateLimiter,
	handler func(*melody.Session, []byte),
) func(*melody.Session, []byte)

WebSocketRateLimitHandler wraps a WebSocket message handler with rate limiting. When the per-IP rate limit is exceeded the connection is closed rather than receiving a structured JSON-RPC error: this avoids leaking plaintext frames onto encrypted sessions (which would not match the {"e":...} envelope and could not be decrypted by the client) and gives well-behaved clients an unambiguous "back off and reconnect" signal.

Types

type APIKeyProvider added in v2.9.0

type APIKeyProvider func() []string

APIKeyProvider is a function that returns the current list of API keys. This allows the auth middleware to dynamically fetch keys on each request, supporting hot-reload of configuration.

type AuthConfig added in v2.9.0

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

AuthConfig holds authentication configuration for the API. It uses a provider function to fetch keys dynamically, supporting hot-reload.

func NewAuthConfig added in v2.9.0

func NewAuthConfig(keyProvider APIKeyProvider) *AuthConfig

NewAuthConfig creates a new AuthConfig with a key provider function. The provider is called on each request to get the current list of valid keys, allowing configuration changes to take effect without server restart.

func (*AuthConfig) Enabled added in v2.9.0

func (a *AuthConfig) Enabled() bool

Enabled returns true if authentication is enabled (at least one key configured).

func (*AuthConfig) IsValidKey added in v2.9.0

func (a *AuthConfig) IsValidKey(key string) bool

IsValidKey checks if the provided key is valid using constant-time comparison to prevent timing attacks.

type ClientSession added in v2.11.0

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

ClientSession holds per-WebSocket encryption state. All AEAD calls MUST happen inside the mutex (golang/go#25882, golang-fips/go#187).

func (*ClientSession) AuthToken added in v2.11.0

func (cs *ClientSession) AuthToken() string

AuthToken returns the auth token (immutable after construction, no lock needed).

func (*ClientSession) DecryptIncoming added in v2.11.0

func (cs *ClientSession) DecryptIncoming(ciphertext []byte) ([]byte, error)

DecryptIncoming decrypts with the next expected counter. Caller should close the WebSocket on error.

func (*ClientSession) DecryptSubsequent added in v2.11.0

func (cs *ClientSession) DecryptSubsequent(frame EncryptedFrame) ([]byte, error)

DecryptSubsequent decrypts a subsequent frame (base64 decode + validation).

func (*ClientSession) EncryptOutgoing added in v2.11.0

func (cs *ClientSession) EncryptOutgoing(plaintext []byte) ([]byte, error)

EncryptOutgoing encrypts plaintext and advances the send counter.

func (*ClientSession) EncryptOutgoingFrame added in v2.11.0

func (cs *ClientSession) EncryptOutgoingFrame(plaintext []byte) ([]byte, error)

EncryptOutgoingFrame encrypts and wraps in the {"e":"..."} JSON envelope. Prefer SendEncryptedFrame when writing directly to a WebSocket (holds the lock across encrypt + write to preserve counter order).

func (*ClientSession) SendEncryptedFrame added in v2.11.0

func (cs *ClientSession) SendEncryptedFrame(plaintext []byte, writeFn func([]byte) error) error

SendEncryptedFrame encrypts, wraps, and writes under the mutex so concurrent writers cannot reorder counters. Counter advances only on success — caller should close the connection on error.

type EncryptedFirstFrame added in v2.11.0

type EncryptedFirstFrame struct {
	Ciphertext  string `json:"e"`
	AuthToken   string `json:"t"`
	SessionSalt string `json:"s"`
	Version     int    `json:"v"`
}

EncryptedFirstFrame is the JSON payload sent on the first WebSocket frame to establish an encrypted session.

type EncryptedFrame added in v2.11.0

type EncryptedFrame struct {
	Ciphertext string `json:"e"`
}

EncryptedFrame is the JSON payload sent on subsequent frames after the session has been established.

type EncryptionGateway added in v2.11.0

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

EncryptionGateway owns salt dedup and failed-frame rate limiting across all sessions. Sessions are stored on the melody session after establishment.

func NewEncryptionGateway added in v2.11.0

func NewEncryptionGateway(db database.UserDBI, opts ...EncryptionGatewayOption) *EncryptionGateway

NewEncryptionGateway constructs a EncryptionGateway with default limits.

func (*EncryptionGateway) EstablishSession added in v2.11.0

func (m *EncryptionGateway) EstablishSession(
	frame EncryptedFirstFrame,
	sourceIP string,
) (*ClientSession, []byte, error)

EstablishSession validates, decrypts, and returns a ClientSession for the first encrypted frame. Failures increment the (authToken, sourceIP) rate limiter; callers should close the WebSocket on error.

Non-constant-time: auth token validity is distinguishable by timing, but tokens are already plaintext on the wire and grant no capability without the 32-byte pairing key. If a future credential is NOT public on the wire, these branches MUST be refactored to constant-time.

func (*EncryptionGateway) StartCleanup added in v2.11.0

func (m *EncryptionGateway) StartCleanup(ctx context.Context)

StartCleanup begins the background cleanup goroutine that evicts expired salt and rate-limit entries. The goroutine exits when ctx is canceled. Safe to call multiple times only with distinct contexts; callers should call this once during server startup.

type EncryptionGatewayOption added in v2.11.0

type EncryptionGatewayOption func(*EncryptionGateway)

EncryptionGatewayOption configures an EncryptionGateway at construction time.

func WithClock added in v2.11.0

WithClock overrides the clock used for timestamps (useful for testing).

func WithFailedFrameBlockDuration added in v2.11.0

func WithFailedFrameBlockDuration(d time.Duration) EncryptionGatewayOption

WithFailedFrameBlockDuration overrides the initial block duration.

func WithFailedFrameMaxBlock added in v2.11.0

func WithFailedFrameMaxBlock(d time.Duration) EncryptionGatewayOption

WithFailedFrameMaxBlock overrides the cap on exponential backoff.

func WithFailedFrameThreshold added in v2.11.0

func WithFailedFrameThreshold(n int) EncryptionGatewayOption

WithFailedFrameThreshold overrides the failed-frame block threshold.

func WithMaxFailEntries added in v2.11.0

func WithMaxFailEntries(n int) EncryptionGatewayOption

WithMaxFailEntries overrides the global cap on entries in the failed-frame rate limiter map.

func WithMaxSaltClients added in v2.11.0

func WithMaxSaltClients(n int) EncryptionGatewayOption

WithMaxSaltClients overrides the global cap on distinct clients tracked in the salt deduplication map.

func WithMaxSaltsPerClient added in v2.11.0

func WithMaxSaltsPerClient(n int) EncryptionGatewayOption

WithMaxSaltsPerClient overrides the per-client salt history cap.

func WithSaltWindowTTL added in v2.11.0

func WithSaltWindowTTL(d time.Duration) EncryptionGatewayOption

WithSaltWindowTTL overrides the salt history TTL.

func WithSessionCleanupInterval added in v2.11.0

func WithSessionCleanupInterval(d time.Duration) EncryptionGatewayOption

WithSessionCleanupInterval overrides the cleanup goroutine tick interval.

type IPRateLimiter

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

IPRateLimiter manages rate limiters per IP address for both HTTP and WebSocket

func NewIPRateLimiter

func NewIPRateLimiter() *IPRateLimiter

NewIPRateLimiter creates a new IP-based rate limiter with the default API limits (100 req/min per IP, burst 20).

func NewIPRateLimiterWithLimits added in v2.11.0

func NewIPRateLimiterWithLimits(r rate.Limit, burst int) *IPRateLimiter

NewIPRateLimiterWithLimits creates a new IP-based rate limiter with custom rate and burst settings. Used by the pairing endpoints which require a much more aggressive limit (1 req/sec per IP) to throttle online PIN guessing.

func (*IPRateLimiter) Cleanup

func (rl *IPRateLimiter) Cleanup()

Cleanup removes old entries that haven't been seen recently

func (*IPRateLimiter) GetLimiter

func (rl *IPRateLimiter) GetLimiter(ip string) *rate.Limiter

GetLimiter returns the rate limiter for the given IP

func (*IPRateLimiter) StartCleanup

func (rl *IPRateLimiter) StartCleanup(ctx context.Context)

StartCleanup starts a goroutine to periodically clean up old rate limiters. The cleanup goroutine will stop when the provided context is cancelled.

type IPsProvider added in v2.9.0

type IPsProvider func() []string

IPsProvider is a function that returns the current list of allowed IPs/CIDRs. This allows the IP filter to dynamically fetch the allowlist on each request, supporting hot-reload of configuration.

type LastSeenTracker added in v2.11.0

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

LastSeenTracker batches in-memory LastSeenAt updates and flushes to DB periodically. High-frequency Touches collapse to one UPDATE per interval.

func NewLastSeenTracker added in v2.11.0

func NewLastSeenTracker(db database.UserDBI) *LastSeenTracker

NewLastSeenTracker constructs a tracker bound to the given UserDBI.

func (*LastSeenTracker) Flush added in v2.11.0

func (t *LastSeenTracker) Flush(ctx context.Context)

Flush writes pending updates (errors logged, not fatal). Snapshots under lock so concurrent Touches are not blocked.

func (*LastSeenTracker) StartFlushLoop added in v2.11.0

func (t *LastSeenTracker) StartFlushLoop(ctx context.Context, interval time.Duration) <-chan struct{}

StartFlushLoop runs Flush periodically with a final flush on shutdown. Pass zero interval for DefaultLastSeenFlushInterval. The returned channel is closed after the final flush has completed.

func (*LastSeenTracker) Touch added in v2.11.0

func (t *LastSeenTracker) Touch(authToken string, ts int64)

Touch records the latest seen timestamp (newest wins, goroutine-safe).

Jump to

Keyboard shortcuts

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