 131868bdca
			
		
	
	131868bdca
	
	
	
		
			
			Major security, observability, and configuration improvements:
## Security Hardening
- Implemented configurable CORS (no more wildcards)
- Added comprehensive auth middleware for admin endpoints
- Enhanced webhook HMAC validation
- Added input validation and rate limiting
- Security headers and CSP policies
## Configuration Management
- Made N8N webhook URL configurable (WHOOSH_N8N_BASE_URL)
- Replaced all hardcoded endpoints with environment variables
- Added feature flags for LLM vs heuristic composition
- Gitea fetch hardening with EAGER_FILTER and FULL_RESCAN options
## API Completeness
- Implemented GetCouncilComposition function
- Added GET /api/v1/councils/{id} endpoint
- Council artifacts API (POST/GET /api/v1/councils/{id}/artifacts)
- /admin/health/details endpoint with component status
- Database lookup for repository URLs (no hardcoded fallbacks)
## Observability & Performance
- Added OpenTelemetry distributed tracing with goal/pulse correlation
- Performance optimization database indexes
- Comprehensive health monitoring
- Enhanced logging and error handling
## Infrastructure
- Production-ready P2P discovery (replaces mock implementation)
- Removed unused Redis configuration
- Enhanced Docker Swarm integration
- Added migration files for performance indexes
## Code Quality
- Comprehensive input validation
- Graceful error handling and failsafe fallbacks
- Backwards compatibility maintained
- Following security best practices
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
		
	
		
			
				
	
	
		
			152 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			152 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package middleware
 | |
| 
 | |
| import (
 | |
| 	"net/http"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // RouteHeaders is a neat little header-based router that allows you to direct
 | |
| // the flow of a request through a middleware stack based on a request header.
 | |
| //
 | |
| // For example, lets say you'd like to setup multiple routers depending on the
 | |
| // request Host header, you could then do something as so:
 | |
| //
 | |
| //	r := chi.NewRouter()
 | |
| //	rSubdomain := chi.NewRouter()
 | |
| //	r.Use(middleware.RouteHeaders().
 | |
| //		Route("Host", "example.com", middleware.New(r)).
 | |
| //		Route("Host", "*.example.com", middleware.New(rSubdomain)).
 | |
| //		Handler)
 | |
| //	r.Get("/", h)
 | |
| //	rSubdomain.Get("/", h2)
 | |
| //
 | |
| // Another example, imagine you want to setup multiple CORS handlers, where for
 | |
| // your origin servers you allow authorized requests, but for third-party public
 | |
| // requests, authorization is disabled.
 | |
| //
 | |
| //	r := chi.NewRouter()
 | |
| //	r.Use(middleware.RouteHeaders().
 | |
| //		Route("Origin", "https://app.skyweaver.net", cors.Handler(cors.Options{
 | |
| //			AllowedOrigins:   []string{"https://api.skyweaver.net"},
 | |
| //			AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
 | |
| //			AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type"},
 | |
| //			AllowCredentials: true, // <----------<<< allow credentials
 | |
| //		})).
 | |
| //		Route("Origin", "*", cors.Handler(cors.Options{
 | |
| //			AllowedOrigins:   []string{"*"},
 | |
| //			AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
 | |
| //			AllowedHeaders:   []string{"Accept", "Content-Type"},
 | |
| //			AllowCredentials: false, // <----------<<< do not allow credentials
 | |
| //		})).
 | |
| //		Handler)
 | |
| func RouteHeaders() HeaderRouter {
 | |
| 	return HeaderRouter{}
 | |
| }
 | |
| 
 | |
| type HeaderRouter map[string][]HeaderRoute
 | |
| 
 | |
| func (hr HeaderRouter) Route(header, match string, middlewareHandler func(next http.Handler) http.Handler) HeaderRouter {
 | |
| 	header = strings.ToLower(header)
 | |
| 	k := hr[header]
 | |
| 	if k == nil {
 | |
| 		hr[header] = []HeaderRoute{}
 | |
| 	}
 | |
| 	hr[header] = append(hr[header], HeaderRoute{MatchOne: NewPattern(match), Middleware: middlewareHandler})
 | |
| 	return hr
 | |
| }
 | |
| 
 | |
| func (hr HeaderRouter) RouteAny(header string, match []string, middlewareHandler func(next http.Handler) http.Handler) HeaderRouter {
 | |
| 	header = strings.ToLower(header)
 | |
| 	k := hr[header]
 | |
| 	if k == nil {
 | |
| 		hr[header] = []HeaderRoute{}
 | |
| 	}
 | |
| 	patterns := []Pattern{}
 | |
| 	for _, m := range match {
 | |
| 		patterns = append(patterns, NewPattern(m))
 | |
| 	}
 | |
| 	hr[header] = append(hr[header], HeaderRoute{MatchAny: patterns, Middleware: middlewareHandler})
 | |
| 	return hr
 | |
| }
 | |
| 
 | |
| func (hr HeaderRouter) RouteDefault(handler func(next http.Handler) http.Handler) HeaderRouter {
 | |
| 	hr["*"] = []HeaderRoute{{Middleware: handler}}
 | |
| 	return hr
 | |
| }
 | |
| 
 | |
| func (hr HeaderRouter) Handler(next http.Handler) http.Handler {
 | |
| 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | |
| 		if len(hr) == 0 {
 | |
| 			// skip if no routes set
 | |
| 			next.ServeHTTP(w, r)
 | |
| 		}
 | |
| 
 | |
| 		// find first matching header route, and continue
 | |
| 		for header, matchers := range hr {
 | |
| 			headerValue := r.Header.Get(header)
 | |
| 			if headerValue == "" {
 | |
| 				continue
 | |
| 			}
 | |
| 			headerValue = strings.ToLower(headerValue)
 | |
| 			for _, matcher := range matchers {
 | |
| 				if matcher.IsMatch(headerValue) {
 | |
| 					matcher.Middleware(next).ServeHTTP(w, r)
 | |
| 					return
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// if no match, check for "*" default route
 | |
| 		matcher, ok := hr["*"]
 | |
| 		if !ok || matcher[0].Middleware == nil {
 | |
| 			next.ServeHTTP(w, r)
 | |
| 			return
 | |
| 		}
 | |
| 		matcher[0].Middleware(next).ServeHTTP(w, r)
 | |
| 	})
 | |
| }
 | |
| 
 | |
| type HeaderRoute struct {
 | |
| 	Middleware func(next http.Handler) http.Handler
 | |
| 	MatchOne   Pattern
 | |
| 	MatchAny   []Pattern
 | |
| }
 | |
| 
 | |
| func (r HeaderRoute) IsMatch(value string) bool {
 | |
| 	if len(r.MatchAny) > 0 {
 | |
| 		for _, m := range r.MatchAny {
 | |
| 			if m.Match(value) {
 | |
| 				return true
 | |
| 			}
 | |
| 		}
 | |
| 	} else if r.MatchOne.Match(value) {
 | |
| 		return true
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| type Pattern struct {
 | |
| 	prefix   string
 | |
| 	suffix   string
 | |
| 	wildcard bool
 | |
| }
 | |
| 
 | |
| func NewPattern(value string) Pattern {
 | |
| 	p := Pattern{}
 | |
| 	if i := strings.IndexByte(value, '*'); i >= 0 {
 | |
| 		p.wildcard = true
 | |
| 		p.prefix = value[0:i]
 | |
| 		p.suffix = value[i+1:]
 | |
| 	} else {
 | |
| 		p.prefix = value
 | |
| 	}
 | |
| 	return p
 | |
| }
 | |
| 
 | |
| func (p Pattern) Match(v string) bool {
 | |
| 	if !p.wildcard {
 | |
| 		return p.prefix == v
 | |
| 	}
 | |
| 	return len(v) >= len(p.prefix+p.suffix) && strings.HasPrefix(v, p.prefix) && strings.HasSuffix(v, p.suffix)
 | |
| }
 |