Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions api/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,30 @@ func setupRoutes(router *gin.Engine, h *api.Handler, userHandler *handlers.UserH
}
}

// corsMiddleware configures Cross-Origin Resource Sharing (CORS) for the API.
// This middleware enables the web UI to communicate with the API backend when they
// are served from different origins (domains/ports).
//
// SECURITY FEATURES:
// - Origin validation: Only explicitly allowed origins can access the API
// - Credential support: Allows cookies and authorization headers in CORS requests
// - WebSocket support: Includes headers required for WebSocket upgrade handshake
//
// WEBSOCKET HEADERS:
// The following headers are essential for WebSocket connections to work:
// - Upgrade: Indicates protocol upgrade from HTTP to WebSocket
// - Connection: Specifies the connection should be upgraded
// - Sec-WebSocket-Key: Client-generated key for handshake
// - Sec-WebSocket-Version: WebSocket protocol version
// - Sec-WebSocket-Extensions: Optional WebSocket extensions
// - Sec-WebSocket-Protocol: Sub-protocol negotiation
//
// CONFIGURATION:
// Set CORS_ALLOWED_ORIGINS environment variable with comma-separated list of origins:
// Example: CORS_ALLOWED_ORIGINS="https://app.streamspace.io,https://admin.streamspace.io"
//
// DEVELOPMENT:
// If not configured, defaults to localhost:3000,8000 for local development
func corsMiddleware() gin.HandlerFunc {
// SECURITY: Get allowed origins from environment
allowedOriginsEnv := getEnv("CORS_ALLOWED_ORIGINS", "")
Expand Down Expand Up @@ -843,6 +867,9 @@ func corsMiddleware() gin.HandlerFunc {
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
}

// Allow standard HTTP headers plus WebSocket upgrade headers
// WebSocket headers (Upgrade, Connection, Sec-WebSocket-*) are required for
// real-time features like session updates and VNC connections
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, Upgrade, Connection, Sec-WebSocket-Key, Sec-WebSocket-Version, Sec-WebSocket-Extensions, Sec-WebSocket-Protocol")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, PATCH, DELETE")

Expand Down
14 changes: 13 additions & 1 deletion api/internal/auth/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,22 @@ import (
"github.com/streamspace/streamspace/api/internal/db"
)

// Middleware creates an authentication middleware
// Middleware creates an authentication middleware that validates JWT tokens
// and ensures user accounts are active.
//
// WEBSOCKET HANDLING:
// WebSocket upgrade requests receive special treatment to maintain protocol compatibility:
// - Detected by checking Upgrade=websocket and Connection=Upgrade headers
// - On auth failure: Returns status code only (no JSON body) via AbortWithStatus
// - Rationale: WebSocket upgrader expects clean HTTP responses without body content
// - Standard requests: Returns JSON error messages as usual
//
// This dual-response approach was added to fix WebSocket connection issues where
// JSON error responses would interfere with the WebSocket handshake protocol.
func Middleware(jwtManager *JWTManager, userDB *db.UserDB) gin.HandlerFunc {
return func(c *gin.Context) {
// Check if this is a WebSocket upgrade request
// WebSocket requests need special error handling (status code only, no JSON body)
isWebSocket := c.GetHeader("Upgrade") == "websocket" && c.GetHeader("Connection") == "Upgrade"

// Extract token from Authorization header
Expand Down
Loading