middleware

package
v0.260813.2 Latest Latest
Warning

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

Go to latest
Published: Aug 13, 2026 License: MPL-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package middleware provides the Gin middleware used by the tingly-box server: HTTP access logging, CORS, authentication, response compression, per-route rate limiting, and IO-deadline management for streaming routes.

Global vs per-route

Only a thin stack runs globally on every request (server.setupMiddleware in internal/server/server_routes.go):

Request
  │
  ├─ gin.Recovery   — panic → 500, prevents process crash
  ├─ MemoryLog      — structured HTTP access log + in-memory ring buffer
  └─ CORS           — Access-Control-* headers

Auth, rate limiting, gzip, and IO-timeout clearing are NOT global — each is applied to the specific route group that needs it at registration time.

Components

MemoryLog (memory_log.go)

Logs one structured entry per request — method, path, status, latency, error — to the multi-mode logger (text + JSON files via pkg/obs.MultiLogger) and an in-memory ring buffer (50 entries per source, pkg/obs.defaultMemorySinkEntries).

For AI-routed requests, the entry is enriched with routing metadata after the handler returns. These fields are written into the gin context by SetTrackingContext (internal/protocolserver/tracking_context.go) and read back here:

  • request_model — model name the client requested
  • routed_model — model name actually forwarded to the provider
  • routed_provider — provider name selected by the routing pipeline
  • api_style — provider API style (e.g. openai, anthropic)
  • base_url — provider API base URL
  • scenario — agent scenario (e.g. "claude_code", "openai")
  • lb_service_id — load-balancer service id chosen for this request
  • lb_tactic — load-balancer tactic name

Non-AI routes (system/management APIs) produce no routing fields.

The access log deliberately records no request/response bodies. Mirroring bodies here (wrapping c.Request.Body / c.Writer) is unstable — it interferes with streaming, Flush/Hijack, and large or Expect-100-continue uploads — for little gain. Bodies that matter for diagnosis are recorded where they are understood: the handler, and the model_request client stage (correlated to this entry by request_id).

AuthMiddleware (auth.go)

Two distinct auth modes, each applied to its route group at registration:

  • UserAuthMiddleware — web-UI / management routes; validates a static bearer token from config; on success sets user_id to the default admin (db.DefaultAdminUserID) so usage records have a stable owner.

  • ModelAuthMiddleware — AI-endpoint routes; supports three methods in priority order: 1. JWT API tokens (multi-tenant, "tb-share-*" prefix, validated from DB) 2. Global config model token ("tingly-box-*" prefix) 3. Enterprise context JWT (X-TBE-Context-JWT header, HS256/RS256) A token carrying the "sk-tbe-" virtual-key prefix is rejected here with a pointer to the dedicated /tbe/* endpoints.

CORS (cors.go)

Applies permissive Access-Control-Allow-* headers required for the single-page web UI. Preflight OPTIONS requests are handled and short- circuited before auth runs.

Gzip (gzip.go)

Per-route response compression for endpoints that can return large JSON (usage stats, time series, records). Registered via swagger.WithMiddleware(middleware.Gzip()); never on streaming/SSE routes.

RateLimit (ratelimit.go)

A fixed-window failed-attempt blocker keyed by client IP: after maxAttempts POSTs within windowSize the IP is blocked for blockDuration. It is scoped to specific auth paths passed to RateLimitMiddleware. Note: it is a failed- attempt limiter, not a token bucket, and it is not wired into the default route stack today.

ClearServerIOTimeouts (io_timeout.go)

Applied to the AI protocol route groups (/tingly/:scenario and /tingly/:scenario/v1, see internal/protocolserver/routes.go) only. Clears the per-connection read/write deadlines armed by http.Server's ReadTimeout/WriteTimeout so long-running SSE streams and large request bodies are bounded by the upstream provider timeout and client disconnect, not by wall-clock from request start (issue #1384).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BaseURLFromRequest

func BaseURLFromRequest(c *gin.Context, defaultPort int) string

BaseURLFromRequest returns the base URL the client used to reach the server, honoring the X-Forwarded-Proto header set by reverse proxies. defaultPort is appended when the request Host carries no explicit port. This is the URL echoed back to clients (e.g. baked into generated agent configs), so it must reflect what the user actually connected to rather than the bind address.

func CORS

func CORS() gin.HandlerFunc

CORS returns a CORS middleware handler

func CORSWithConfig

func CORSWithConfig(config CORSConfig) gin.HandlerFunc

CORSWithConfig returns a CORS middleware handler with custom configuration

func ClearServerIOTimeouts

func ClearServerIOTimeouts() gin.HandlerFunc

ClearServerIOTimeouts removes the per-connection read/write deadlines that http.Server arms from its ReadTimeout/WriteTimeout for the current request.

The server-wide WriteTimeout is armed once, when the request headers are read, and is never extended by subsequent writes. AI sampling requests are bounded by the upstream provider timeout (provider.Timeout, default 1800s) plus failover attempts — not by wall-clock from request start — so any SSE stream that outlives WriteTimeout gets its TCP connection killed mid-stream and the client sees EOF without a terminal event (Codex: "stream closed before response.completed", issue #1384). ReadTimeout similarly caps reading the request body, which agentic clients fill with the entire conversation (tens of MB) on every turn.

Applied per-group to the AI protocol endpoints only; management/UI routes keep the server-wide protection. Request lifetime on these routes remains bounded by the upstream timeout and by client-disconnect cancellation of the request context.

func Gzip

func Gzip() gin.HandlerFunc

Gzip returns gin middleware that gzip-compresses the response body when the client accepts it. Intended for endpoints that can return large JSON payloads (usage stats, time series, records) — register it per-route via swagger.WithMiddleware(middleware.Gzip()) rather than wrapping the handler directly, so it composes through the normal auth/CORS middleware chain instead of bypassing it. Do not use it on streaming/SSE endpoints.

func RateLimitMiddleware

func RateLimitMiddleware(rl *RateLimiter, authPaths ...string) gin.HandlerFunc

RateLimitMiddleware returns a Gin middleware for rate limiting This is specifically for auth endpoints (handshake, execute)

Types

type APITokenStore

type APITokenStore interface {
	ValidateToken(tokenID string) (*db.APITokenRecord, error)
	UpdateLastUsed(tokenID string) error
}

APITokenStore interface for token validation (abstracted for testability)

type AuthMiddleware

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

AuthMiddleware provides authentication middleware for different types of authentication

func NewAuthMiddleware

func NewAuthMiddleware(cfg *config.Config, jwtManager *auth.JWTManager, apiTokenManager *auth.APITokenManager, apiTokenStore APITokenStore) *AuthMiddleware

NewAuthMiddleware creates a new authentication middleware

func (*AuthMiddleware) ModelAuthMiddleware

func (am *AuthMiddleware) ModelAuthMiddleware() gin.HandlerFunc

ModelAuthMiddleware middleware for OpenAI and Anthropic API authentication The auth will support both `Authorization` and `X-Api-Key` Supports three authentication methods (in order of precedence): 1. JWT API tokens (when multi-tenant is enabled) 2. Global config model token (backward compatibility) 3. Enterprise context JWT (X-TBE-Context-JWT header)

func (*AuthMiddleware) UserAuthMiddleware

func (am *AuthMiddleware) UserAuthMiddleware() gin.HandlerFunc

UserAuthMiddleware middleware for UI and control API authentication

type CORSConfig

type CORSConfig struct {
	AllowOrigins    string
	AllowMethods    string
	AllowHeaders    string
	ExposeHeaders   string
	MaxAge          int
	HandlePreflight bool
}

CORSConfig defines the configuration for CORS middleware

type ErrorDetail

type ErrorDetail struct {
	Message string `json:"message"`
	Type    string `json:"type"`
	Code    string `json:"code,omitempty"`
}

ErrorDetail represents error details

type ErrorResponse

type ErrorResponse struct {
	Error ErrorDetail `json:"error"`
}

ErrorResponse represents an error response

type MemoryLog

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

MemoryLog is the HTTP access log for the whole request chain. It records one structured entry per request — method, path, status, latency, error, and (for AI routes) routing metadata — correlated across stages by a request_id. Entries go to the multi-mode logger (text + JSON files) and an in-memory ring buffer for the logs API.

It deliberately does NOT capture request/response bodies. Opportunistically mirroring bodies here (wrapping c.Request.Body / c.Writer) is unstable — it interferes with streaming, Flush/Hijack, and large/Expect-100-continue uploads — for little gain: the bodies that matter for diagnosis are recorded where they are understood (the handler and the model_request client stage).

func NewMemoryLogMiddleware

func NewMemoryLogMiddleware(multiLogger *obs.MultiLogger) *MemoryLog

NewMemoryLogMiddleware creates the HTTP access log middleware.

func (*MemoryLog) Clear

func (m *MemoryLog) Clear()

Clear removes all log entries from memory

func (*MemoryLog) GetEntries

func (m *MemoryLog) GetEntries() []*logrus.Entry

GetEntries returns all log entries from memory in chronological order

func (*MemoryLog) GetEntriesByLevel

func (m *MemoryLog) GetEntriesByLevel(level logrus.Level) []*logrus.Entry

GetEntriesByLevel returns log entries from memory matching the specified level

func (*MemoryLog) GetEntriesSince

func (m *MemoryLog) GetEntriesSince(since time.Time) []*logrus.Entry

GetEntriesSince returns log entries from memory after the specified time

func (*MemoryLog) GetLatestEntries

func (m *MemoryLog) GetLatestEntries(n int) []*logrus.Entry

GetLatestEntries returns the newest N log entries from memory

func (*MemoryLog) Middleware

func (m *MemoryLog) Middleware() gin.HandlerFunc

Middleware returns a Gin middleware compatible with gin.Logger() It logs all HTTP requests to both the multi-mode logger and memory

func (*MemoryLog) Size

func (m *MemoryLog) Size() int

Size returns the current number of stored log entries in memory

type RateLimiter

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

RateLimiter provides rate limiting functionality

func NewRateLimiter

func NewRateLimiter(maxAttempts int, windowSize, blockDuration time.Duration) *RateLimiter

NewRateLimiter creates a new rate limiter

func (*RateLimiter) Cleanup

func (rl *RateLimiter) Cleanup()

cleanup runs periodically to remove expired entries

func (*RateLimiter) GetStats

func (rl *RateLimiter) GetStats() map[string]interface{}

GetStats returns rate limiting statistics

func (*RateLimiter) ResetIP

func (rl *RateLimiter) ResetIP(ip string)

ResetIP resets the rate limit for a specific IP (admin use only)

Jump to

Keyboard shortcuts

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