- 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
85 lines
2.0 KiB
Go
85 lines
2.0 KiB
Go
package web
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"io/fs"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
|
|
"github.com/salvacybersec/keyhunter/pkg/dorks"
|
|
"github.com/salvacybersec/keyhunter/pkg/providers"
|
|
"github.com/salvacybersec/keyhunter/pkg/recon"
|
|
"github.com/salvacybersec/keyhunter/pkg/storage"
|
|
)
|
|
|
|
// Config holds all dependencies and settings needed by the web server.
|
|
type Config struct {
|
|
DB *storage.DB
|
|
EncKey []byte
|
|
Providers *providers.Registry
|
|
Dorks *dorks.Registry
|
|
ReconEngine *recon.Engine
|
|
Port int
|
|
AuthUser string
|
|
AuthPass string
|
|
AuthToken string
|
|
}
|
|
|
|
// Server is the KeyHunter web dashboard backed by chi.
|
|
type Server struct {
|
|
router chi.Router
|
|
cfg Config
|
|
tmpl *template.Template
|
|
}
|
|
|
|
// NewServer creates a Server, parsing embedded templates and building routes.
|
|
func NewServer(cfg Config) (*Server, error) {
|
|
// Parse all templates from the embedded filesystem.
|
|
tmpl, err := template.ParseFS(templateFiles, "templates/*.html")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing templates: %w", err)
|
|
}
|
|
|
|
s := &Server{
|
|
cfg: cfg,
|
|
tmpl: tmpl,
|
|
}
|
|
|
|
r := chi.NewRouter()
|
|
|
|
// Middleware stack.
|
|
r.Use(middleware.RealIP)
|
|
r.Use(middleware.Logger)
|
|
r.Use(middleware.Recoverer)
|
|
|
|
// Auth middleware (no-op when auth fields are empty).
|
|
r.Use(AuthMiddleware(cfg.AuthUser, cfg.AuthPass, cfg.AuthToken))
|
|
|
|
// Static file serving from embedded FS.
|
|
staticSub, err := fs.Sub(staticFiles, "static")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating static sub-filesystem: %w", err)
|
|
}
|
|
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.FS(staticSub))))
|
|
|
|
// Page routes.
|
|
r.Get("/", s.handleOverview)
|
|
|
|
s.router = r
|
|
return s, nil
|
|
}
|
|
|
|
// Router returns the chi router for testing.
|
|
func (s *Server) Router() chi.Router {
|
|
return s.router
|
|
}
|
|
|
|
// ListenAndServe starts the HTTP server on the configured port.
|
|
func (s *Server) ListenAndServe() error {
|
|
addr := fmt.Sprintf(":%d", s.cfg.Port)
|
|
return http.ListenAndServe(addr, s.router)
|
|
}
|