130187c7be
P0 fixes: - WebSocket origin checking (reject untrusted origins) - WS session token moved from URL query param to first message frame - WebAuthn login cookie now uses Secure flag via shared SetSessionCookie - PostgreSQL sslmode configurable via POSTGRES_SSLMODE env (default: require) - Fixed DatabaseDSN to use real password instead of masked placeholder P1 fixes: - Per-IP rate limiting middleware (auth: 5 req/s, invites: 2 req/s) - Security headers on all responses (CSP, X-Frame-Options, nosniff, etc.) - WS broadcasts scoped to server members (prevents cross-server data leak) - Webhook tokens stored as SHA-256 hashes (not plaintext) P2 fixes: - CSRF protection via Origin header validation on state-changing requests - MANAGE_CHANNELS permission enforced on channel update/delete - Upload validation: 25MB limit, extension allowlist, server-side MIME check - File serve: path traversal protection + Content-Disposition: attachment Files: 23 changed, +499/-100. Builds clean (go build, go vet, npm build). Zero CVEs (govulncheck, npm audit).
50 lines
1.4 KiB
Go
50 lines
1.4 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// CSRFProtect returns middleware that validates the Origin header on
|
|
// state-changing methods (POST, PUT, PATCH, DELETE). Requests from origins
|
|
// not in the trustedOrigins list are rejected with 403. Non-browser clients
|
|
// (curl, bots) that send no Origin or Referer header are allowed through,
|
|
// since they are not subject to CSRF.
|
|
func CSRFProtect(trustedOrigins []string) func(http.Handler) http.Handler {
|
|
originSet := make(map[string]struct{}, len(trustedOrigins))
|
|
for _, o := range trustedOrigins {
|
|
originSet[o] = struct{}{}
|
|
}
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case "POST", "PUT", "PATCH", "DELETE":
|
|
origin := r.Header.Get("Origin")
|
|
if origin == "" {
|
|
// Fallback: extract origin from Referer
|
|
referer := r.Header.Get("Referer")
|
|
if referer != "" {
|
|
for _, o := range trustedOrigins {
|
|
if strings.HasPrefix(referer, o) {
|
|
origin = o
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if origin != "" {
|
|
if _, ok := originSet[origin]; !ok {
|
|
http.Error(w, `{"error":"forbidden origin"}`, http.StatusForbidden)
|
|
return
|
|
}
|
|
}
|
|
// If both Origin and Referer are empty, allow through
|
|
// (non-browser client like curl, bots, mobile apps)
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|