Cybersecurity-Projects/PROJECTS/advanced/honeypot-network/internal/api/middleware.go

161 lines
3.1 KiB
Go

/*
©AngelaMos | 2026
middleware.go
HTTP middleware chain for the dashboard REST API
Provides request logging with structured JSON output, panic
recovery that returns 500 without crashing the process, and CORS
handling for browser-based dashboard clients. Each middleware is
a standard func(http.Handler) http.Handler closure.
*/
package api
import (
"bufio"
"fmt"
"net"
"net/http"
"time"
"github.com/rs/zerolog"
)
type statusWriter struct {
http.ResponseWriter
status int
size int
}
func (w *statusWriter) WriteHeader(code int) {
w.status = code
w.ResponseWriter.WriteHeader(code)
}
func (w *statusWriter) Write(b []byte) (int, error) {
n, err := w.ResponseWriter.Write(b)
w.size += n
return n, err
}
func (w *statusWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if hj, ok := w.ResponseWriter.(http.Hijacker); ok {
return hj.Hijack()
}
return nil, nil, fmt.Errorf(
"underlying ResponseWriter does not implement http.Hijacker",
)
}
func (w *statusWriter) Unwrap() http.ResponseWriter {
return w.ResponseWriter
}
func requestLogger(
logger *zerolog.Logger,
) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(
func(
w http.ResponseWriter,
r *http.Request,
) {
start := time.Now()
sw := &statusWriter{
ResponseWriter: w,
status: http.StatusOK,
}
next.ServeHTTP(sw, r)
logger.Info().
Str("method", r.Method).
Str("path", r.URL.Path).
Int("status", sw.status).
Int("bytes", sw.size).
Dur("latency", time.Since(start)).
Str("remote", r.RemoteAddr).
Msg("api request")
},
)
}
}
func recoverer(
logger *zerolog.Logger,
) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(
func(
w http.ResponseWriter,
r *http.Request,
) {
defer func() {
if rec := recover(); rec != nil {
logger.Error().
Interface("panic", rec).
Str("path", r.URL.Path).
Str("method", r.Method).
Msg("recovered from panic")
http.Error(
w,
http.StatusText(
http.StatusInternalServerError,
),
http.StatusInternalServerError,
)
}
}()
next.ServeHTTP(w, r)
},
)
}
}
func corsMiddleware(
origins []string,
) func(http.Handler) http.Handler {
allowed := make(map[string]bool, len(origins))
for _, o := range origins {
allowed[o] = true
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(
func(
w http.ResponseWriter,
r *http.Request,
) {
origin := r.Header.Get("Origin")
if allowed[origin] || allowed["*"] {
w.Header().Set(
"Access-Control-Allow-Origin",
origin,
)
}
w.Header().Set(
"Access-Control-Allow-Methods",
"GET, POST, OPTIONS",
)
w.Header().Set(
"Access-Control-Allow-Headers",
"Content-Type, Authorization",
)
w.Header().Set(
"Access-Control-Max-Age",
corsMaxAgeSeconds,
)
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
},
)
}
}