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) }