package server import ( "net/http" "net/url" "strings" ) const ( sessionCookieName = "ddc_session" maxRequestBodyBytes = 64 << 10 contentSecurityPolicy = "default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'" ) func securityHeaders(next http.Handler) http.Handler { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { header := writer.Header() header.Set("Content-Security-Policy", contentSecurityPolicy) header.Set("X-Content-Type-Options", "nosniff") header.Set("X-Frame-Options", "DENY") header.Set("Referrer-Policy", "no-referrer") header.Set("Cross-Origin-Opener-Policy", "same-origin") header.Set("Permissions-Policy", "camera=(), microphone=(), geolocation=()") if strings.HasPrefix(request.URL.Path, "/api/") { header.Set("Cache-Control", "no-store") } next.ServeHTTP(writer, request) }) } func protectStateChanges(next http.Handler) http.Handler { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { if request.Method == http.MethodGet || request.Method == http.MethodHead { next.ServeHTTP(writer, request) return } if !isSameOrigin(request) { writeError(writer, http.StatusForbidden, "cross-origin request rejected") return } if request.Method == http.MethodPost && !strings.HasPrefix(request.Header.Get("Content-Type"), "application/json") { writeError(writer, http.StatusUnsupportedMediaType, "content type must be application/json") return } request.Body = http.MaxBytesReader(writer, request.Body, maxRequestBodyBytes) next.ServeHTTP(writer, request) }) } func isSameOrigin(request *http.Request) bool { origin := request.Header.Get("Origin") if origin == "" { fetchSite := request.Header.Get("Sec-Fetch-Site") return fetchSite == "" || fetchSite == "same-origin" || fetchSite == "none" } parsed, err := url.Parse(origin) return err == nil && strings.EqualFold(parsed.Host, request.Host) } func (server *Server) requireSession(next http.Handler) http.Handler { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { if !server.authenticated(request) { writeError(writer, http.StatusUnauthorized, "authentication required") return } next.ServeHTTP(writer, request) }) } func (server *Server) authenticated(request *http.Request) bool { cookie, err := request.Cookie(sessionCookieName) return err == nil && server.sessions.Valid(cookie.Value) }