74 lines
2.5 KiB
Go
Executable File
74 lines
2.5 KiB
Go
Executable File
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)
|
|
}
|