feat(18-01): implement chi server, auth middleware, overview handler

- Server struct with chi router, embedded template parsing, static file serving
- AuthMiddleware supports Basic and Bearer token with constant-time comparison
- Overview handler renders stats from providers/recon/storage when available
- Nil-safe: works with zero config (shows zeroes, no DB required)
- All 7 tests pass
This commit is contained in:
salvacybersec
2026-04-06 18:02:41 +03:00
parent 3541c82448
commit 268a769efb
3 changed files with 194 additions and 0 deletions

52
pkg/web/auth.go Normal file
View File

@@ -0,0 +1,52 @@
package web
import (
"crypto/subtle"
"net/http"
"strings"
)
// AuthMiddleware returns HTTP middleware that enforces authentication when
// at least one of user/pass or token is configured. If all auth fields are
// empty, the middleware is a no-op passthrough.
//
// Supported schemes:
// - Bearer <token> — matches the configured token
// - Basic <base64> — matches the configured user:pass
func AuthMiddleware(user, pass, token string) func(http.Handler) http.Handler {
authEnabled := user != "" || token != ""
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !authEnabled {
next.ServeHTTP(w, r)
return
}
auth := r.Header.Get("Authorization")
// Check bearer token first.
if token != "" && strings.HasPrefix(auth, "Bearer ") {
provided := strings.TrimPrefix(auth, "Bearer ")
if subtle.ConstantTimeCompare([]byte(provided), []byte(token)) == 1 {
next.ServeHTTP(w, r)
return
}
}
// Check basic auth.
if user != "" {
u, p, ok := r.BasicAuth()
if ok &&
subtle.ConstantTimeCompare([]byte(u), []byte(user)) == 1 &&
subtle.ConstantTimeCompare([]byte(p), []byte(pass)) == 1 {
next.ServeHTTP(w, r)
return
}
}
w.Header().Set("WWW-Authenticate", `Basic realm="keyhunter"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
})
}
}