10 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 18-web-dashboard | 01 | execute | 1 |
|
true |
|
|
Purpose: Establishes the HTTP server skeleton that Plans 02 and 03 build upon.
Output: Working pkg/web package with chi router, static serving, layout template, overview page, auth middleware.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/18-web-dashboard/18-CONTEXT.mdFrom pkg/storage/db.go:
type DB struct { ... }
func Open(path string) (*DB, error)
func (db *DB) Close() error
func (db *DB) SQL() *sql.DB
From pkg/storage/findings.go:
type Finding struct {
ID, ScanID int64
ProviderName string
KeyValue, KeyMasked, Confidence string
SourcePath, SourceType string
LineNumber int
CreatedAt time.Time
Verified bool
VerifyStatus string
VerifyHTTPCode int
VerifyMetadata map[string]string
}
func (db *DB) ListFindings(encKey []byte) ([]Finding, error)
func (db *DB) SaveFinding(f Finding, encKey []byte) (int64, error)
From pkg/storage/queries.go:
type Filters struct {
Provider, Confidence, SourceType string
Verified *bool
Limit, Offset int
}
func (db *DB) ListFindingsFiltered(encKey []byte, f Filters) ([]Finding, error)
func (db *DB) GetFinding(id int64, encKey []byte) (*Finding, error)
func (db *DB) DeleteFinding(id int64) (int64, error)
From pkg/providers/registry.go:
type Registry struct { ... }
func NewRegistry() (*Registry, error)
func (r *Registry) List() []Provider
func (r *Registry) Stats() RegistryStats
From pkg/dorks/registry.go:
type Registry struct { ... }
func NewRegistry() (*Registry, error)
func (r *Registry) List() []Dork
func (r *Registry) Stats() Stats
From pkg/recon/engine.go:
type Engine struct { ... }
func NewEngine() *Engine
func (e *Engine) SweepAll(ctx context.Context, cfg Config) ([]Finding, error)
func (e *Engine) List() []string
-
Create
pkg/web/embed.go://go:embed static/*intovar staticFiles embed.FS//go:embed templates/*intovar templateFiles embed.FS- Export both via package-level vars.
-
Download htmx v2.0.4 minified JS (curl from unpkg.com/htmx.org@2.0.4/dist/htmx.min.js) and save to
pkg/web/static/htmx.min.js. -
Create
pkg/web/static/style.csswith minimal custom styles (body font, table styling, card class). The layout will load Tailwind v4 from CDN (https://cdn.tailwindcss.com) per the CONTEXT.md deferred decision. The local style.css is for overrides only. -
Create
pkg/web/templates/layout.html— html/template (NOT templ, per deferred decision):- DOCTYPE, html, head with Tailwind CDN link, htmx.min.js script tag (served from /static/htmx.min.js), local style.css link
- Navigation bar: KeyHunter brand, links to Overview (/), Keys (/keys), Providers (/providers), Recon (/recon), Dorks (/dorks), Settings (/settings)
{{block "content" .}}{{end}}placeholder for page content- Use
{{define "layout"}}...{{end}}wrapping pattern so pages extend it
-
Create
pkg/web/templates/overview.htmlextending layout:{{template "layout" .}}with{{define "content"}}block- Four stat cards in a Tailwind grid (lg:grid-cols-4, sm:grid-cols-2): Total Keys, Providers Loaded, Recon Sources, Last Scan
- Recent findings table showing last 10 keys (masked): Provider, Masked Key, Source, Confidence, Date
- Data struct:
OverviewData{TotalKeys int, TotalProviders int, ReconSources int, LastScan string, RecentFindings []storage.Finding}cd /home/salva/Documents/apikey && go build ./pkg/web/... pkg/web/embed.go compiles with go:embed directives, htmx.min.js is vendored, layout.html and overview.html parse without errors, chi v5 is in go.mod
-
Create
pkg/web/auth.go:func AuthMiddleware(user, pass, token string) func(http.Handler) http.Handler- Check Authorization header: if "Bearer " matches configured token, pass through
- If "Basic " matches user:pass, pass through
- Otherwise return 401 with
WWW-Authenticate: Basic realm="keyhunter"header - If all auth fields are empty strings, middleware is a no-op passthrough
-
Create
pkg/web/handlers.go:type OverviewData struct { TotalKeys, TotalProviders, ReconSources int; LastScan string; RecentFindings []storage.Finding; PageTitle string }func (s *Server) handleOverview(w http.ResponseWriter, r *http.Request)- Query: count findings via
len(db.ListFindingsFiltered(encKey, Filters{Limit: 10}))for recent, run a COUNT query on the SQL for total - Provider count from
s.cfg.Providers.Stats().Total(orlen(s.cfg.Providers.List())) - Recon sources from
len(s.cfg.ReconEngine.List()) - Render overview template with OverviewData
-
Create
pkg/web/server_test.go:- Use
httptest.NewRecorder+httptest.NewRequestagainsts.Router() - Test overview returns 200 with "KeyHunter" in body
- Test static asset serving
- Test auth middleware (401 without creds, 200 with basic auth, 200 with bearer token)
- For DB-dependent tests, use in-memory SQLite (
storage.Open(":memory:")) or skip DB and test the router/auth independently with a nil-safe overview (show zeroes when DB is nil) cd /home/salva/Documents/apikey && go test ./pkg/web/... -v -count=1 Server starts with chi router, static assets served via go:embed, overview page renders with stats, auth middleware blocks unauthenticated requests when configured, all tests pass
- Use
<success_criteria>
- chi v5.2.5 in go.mod
- pkg/web/server.go exports Server, NewServer, Config
- GET / returns overview HTML with stat cards
- GET /static/htmx.min.js returns vendored htmx
- Auth middleware returns 401 when credentials missing (when auth configured)
- Auth middleware passes with valid basic auth or bearer token </success_criteria>