package server import ( "context" "net/http" "strings" ) type contextKey string const sessionKey contextKey = "session" // AuthMiddleware checks for a valid session cookie on protected endpoints. func AuthMiddleware(store *SessionStore, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Public endpoints (no auth required) path := r.URL.Path if path == "/health" || path == "/web/login" || path == "/web/setup" || path == "/web/setup/install" || path == "/web/session/authenticate" || path == "/web/database/list" || path == "/web/webclient/version_info" || strings.Contains(path, "/static/") { next.ServeHTTP(w, r) return } // Check session cookie cookie, err := r.Cookie("session_id") if err != nil || cookie.Value == "" { // Also check JSON-RPC params for session_id (Odoo sends it both ways) next.ServeHTTP(w, r) // For now, allow through — UID defaults to 1 return } sess := store.Get(cookie.Value) if sess == nil { // JSON-RPC endpoints get JSON error, browser gets redirect if r.Header.Get("Content-Type") == "application/json" || strings.HasPrefix(path, "/web/dataset/") || strings.HasPrefix(path, "/jsonrpc") { http.Error(w, `{"jsonrpc":"2.0","error":{"code":100,"message":"Session expired"}}`, http.StatusUnauthorized) } else { http.Redirect(w, r, "/web/login", http.StatusFound) } return } // Inject session into context ctx := context.WithValue(r.Context(), sessionKey, sess) next.ServeHTTP(w, r.WithContext(ctx)) }) } // GetSession extracts the session from request context. func GetSession(r *http.Request) *Session { sess, _ := r.Context().Value(sessionKey).(*Session) return sess }