- Update agent-ops project rules and roadmap files - Add proto-socket infrastructure communication rail milestone - Update Flutter pubspec.lock and contracts notes - Enhance core service: config, HTTP middleware, router - Add notification module improvements - Add protosocket internal package
79 lines
2.1 KiB
Go
79 lines
2.1 KiB
Go
package http
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/subtle"
|
|
"errors"
|
|
"log/slog"
|
|
"net"
|
|
stdhttp "net/http"
|
|
"time"
|
|
)
|
|
|
|
type statusRecorder struct {
|
|
stdhttp.ResponseWriter
|
|
status int
|
|
}
|
|
|
|
func (r *statusRecorder) WriteHeader(status int) {
|
|
r.status = status
|
|
r.ResponseWriter.WriteHeader(status)
|
|
}
|
|
|
|
// Hijack lets WebSocket upgrades (e.g. the proto-socket route) take over the
|
|
// connection even though the response writer is wrapped for status logging.
|
|
func (r *statusRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
hijacker, ok := r.ResponseWriter.(stdhttp.Hijacker)
|
|
if !ok {
|
|
return nil, nil, errors.New("underlying ResponseWriter does not implement http.Hijacker")
|
|
}
|
|
return hijacker.Hijack()
|
|
}
|
|
|
|
func loggingMiddleware(logger *slog.Logger) func(stdhttp.Handler) stdhttp.Handler {
|
|
return func(next stdhttp.Handler) stdhttp.Handler {
|
|
return stdhttp.HandlerFunc(func(w stdhttp.ResponseWriter, r *stdhttp.Request) {
|
|
start := time.Now()
|
|
recorder := &statusRecorder{ResponseWriter: w, status: stdhttp.StatusOK}
|
|
next.ServeHTTP(recorder, r)
|
|
|
|
if logger != nil {
|
|
logger.Info(
|
|
"http request",
|
|
"method", r.Method,
|
|
"path", r.URL.Path,
|
|
"status", recorder.status,
|
|
"duration", time.Since(start).String(),
|
|
)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func basicAuthMiddleware(auth AuthConfig) func(stdhttp.Handler) stdhttp.Handler {
|
|
if auth.Password == "" {
|
|
return func(next stdhttp.Handler) stdhttp.Handler {
|
|
return next
|
|
}
|
|
}
|
|
if auth.Username == "" {
|
|
auth.Username = "nomadcode"
|
|
}
|
|
|
|
return func(next stdhttp.Handler) stdhttp.Handler {
|
|
return stdhttp.HandlerFunc(func(w stdhttp.ResponseWriter, r *stdhttp.Request) {
|
|
username, password, ok := r.BasicAuth()
|
|
if !ok || !secureCompare(username, auth.Username) || !secureCompare(password, auth.Password) {
|
|
w.Header().Set("WWW-Authenticate", `Basic realm="nomadcode-core", charset="UTF-8"`)
|
|
writeError(w, stdhttp.StatusUnauthorized, "authentication required")
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
func secureCompare(got, want string) bool {
|
|
return subtle.ConstantTimeCompare([]byte(got), []byte(want)) == 1
|
|
}
|