246 lines
10 KiB
Markdown
246 lines
10 KiB
Markdown
---
|
|
phase: 18-web-dashboard
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- pkg/web/server.go
|
|
- pkg/web/auth.go
|
|
- pkg/web/handlers.go
|
|
- pkg/web/embed.go
|
|
- pkg/web/static/htmx.min.js
|
|
- pkg/web/static/style.css
|
|
- pkg/web/templates/layout.html
|
|
- pkg/web/templates/overview.html
|
|
- pkg/web/server_test.go
|
|
autonomous: true
|
|
requirements: [WEB-01, WEB-02, WEB-10]
|
|
|
|
must_haves:
|
|
truths:
|
|
- "chi v5 HTTP server starts on configurable port and serves embedded static assets"
|
|
- "Overview page renders with summary statistics from database"
|
|
- "Optional basic auth / token auth blocks unauthenticated requests when configured"
|
|
artifacts:
|
|
- path: "pkg/web/server.go"
|
|
provides: "chi router setup, middleware stack, NewServer constructor"
|
|
exports: ["Server", "NewServer", "Config"]
|
|
- path: "pkg/web/auth.go"
|
|
provides: "Basic auth and bearer token auth middleware"
|
|
exports: ["AuthMiddleware"]
|
|
- path: "pkg/web/handlers.go"
|
|
provides: "Overview page handler with stats aggregation"
|
|
exports: ["handleOverview"]
|
|
- path: "pkg/web/embed.go"
|
|
provides: "go:embed directives for static/ and templates/"
|
|
exports: ["staticFS", "templateFS"]
|
|
- path: "pkg/web/server_test.go"
|
|
provides: "Integration tests for server, auth, overview"
|
|
key_links:
|
|
- from: "pkg/web/server.go"
|
|
to: "pkg/storage"
|
|
via: "DB dependency in Config struct"
|
|
pattern: "storage\\.DB"
|
|
- from: "pkg/web/handlers.go"
|
|
to: "pkg/web/templates/overview.html"
|
|
via: "html/template rendering"
|
|
pattern: "template\\..*Execute"
|
|
- from: "pkg/web/server.go"
|
|
to: "pkg/web/static/"
|
|
via: "go:embed + http.FileServer"
|
|
pattern: "http\\.FileServer"
|
|
---
|
|
|
|
<objective>
|
|
Create the pkg/web package foundation: chi v5 router, go:embed static assets (htmx.min.js, Tailwind CDN reference), html/template-based layout, overview dashboard page with stats, and optional auth middleware.
|
|
|
|
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.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/18-web-dashboard/18-CONTEXT.md
|
|
|
|
<interfaces>
|
|
<!-- Key types and contracts the executor needs. -->
|
|
|
|
From pkg/storage/db.go:
|
|
```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:
|
|
```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:
|
|
```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:
|
|
```go
|
|
type Registry struct { ... }
|
|
func NewRegistry() (*Registry, error)
|
|
func (r *Registry) List() []Provider
|
|
func (r *Registry) Stats() RegistryStats
|
|
```
|
|
|
|
From pkg/dorks/registry.go:
|
|
```go
|
|
type Registry struct { ... }
|
|
func NewRegistry() (*Registry, error)
|
|
func (r *Registry) List() []Dork
|
|
func (r *Registry) Stats() Stats
|
|
```
|
|
|
|
From pkg/recon/engine.go:
|
|
```go
|
|
type Engine struct { ... }
|
|
func NewEngine() *Engine
|
|
func (e *Engine) SweepAll(ctx context.Context, cfg Config) ([]Finding, error)
|
|
func (e *Engine) List() []string
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: chi v5 dependency + go:embed static assets + layout template</name>
|
|
<files>pkg/web/embed.go, pkg/web/static/htmx.min.js, pkg/web/static/style.css, pkg/web/templates/layout.html, pkg/web/templates/overview.html</files>
|
|
<action>
|
|
1. Run `go get github.com/go-chi/chi/v5@v5.2.5` to add chi v5 to go.mod.
|
|
|
|
2. Create `pkg/web/embed.go`:
|
|
- `//go:embed static/*` into `var staticFiles embed.FS`
|
|
- `//go:embed templates/*` into `var templateFiles embed.FS`
|
|
- Export both via package-level vars.
|
|
|
|
3. 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`.
|
|
|
|
4. Create `pkg/web/static/style.css` with 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.
|
|
|
|
5. 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
|
|
|
|
6. Create `pkg/web/templates/overview.html` extending 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}`
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/salva/Documents/apikey && go build ./pkg/web/...</automated>
|
|
</verify>
|
|
<done>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</done>
|
|
</task>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 2: Server struct, auth middleware, overview handler, and tests</name>
|
|
<files>pkg/web/server.go, pkg/web/auth.go, pkg/web/handlers.go, pkg/web/server_test.go</files>
|
|
<behavior>
|
|
- Test: GET / returns 200 with "KeyHunter" in body (overview page renders)
|
|
- Test: GET /static/htmx.min.js returns 200 with JS content
|
|
- Test: GET / with auth enabled but no credentials returns 401
|
|
- Test: GET / with correct basic auth returns 200
|
|
- Test: GET / with correct bearer token returns 200
|
|
- Test: Overview page shows provider count and key count from injected data
|
|
</behavior>
|
|
<action>
|
|
1. Create `pkg/web/server.go`:
|
|
- `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 }` — all fields the server needs
|
|
- `type Server struct { router chi.Router; cfg Config; tmpl *template.Template }`
|
|
- `func NewServer(cfg Config) (*Server, error)` — parses all templates from templateFiles embed.FS, builds chi.Router
|
|
- Router setup: `chi.NewRouter()`, use `middleware.Logger`, `middleware.Recoverer`, `middleware.RealIP`
|
|
- If AuthUser or AuthToken is set, apply AuthMiddleware (from auth.go)
|
|
- Mount `/static/` serving from staticFiles embed.FS (use `http.StripPrefix` + `http.FileServer(http.FS(...))`)
|
|
- Register routes: `GET /` -> handleOverview
|
|
- `func (s *Server) ListenAndServe() error` — starts `http.Server` on `cfg.Port`
|
|
- `func (s *Server) Router() chi.Router` — expose for testing
|
|
|
|
2. Create `pkg/web/auth.go`:
|
|
- `func AuthMiddleware(user, pass, token string) func(http.Handler) http.Handler`
|
|
- Check Authorization header: if "Bearer <token>" matches configured token, pass through
|
|
- If "Basic <base64>" 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
|
|
|
|
3. 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` (or `len(s.cfg.Providers.List())`)
|
|
- Recon sources from `len(s.cfg.ReconEngine.List())`
|
|
- Render overview template with OverviewData
|
|
|
|
4. Create `pkg/web/server_test.go`:
|
|
- Use `httptest.NewRecorder` + `httptest.NewRequest` against `s.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)
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/salva/Documents/apikey && go test ./pkg/web/... -v -count=1</automated>
|
|
</verify>
|
|
<done>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</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `go build ./pkg/web/...` compiles without errors
|
|
- `go test ./pkg/web/... -v` — all tests pass
|
|
- `go vet ./pkg/web/...` — no issues
|
|
</verification>
|
|
|
|
<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>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/18-web-dashboard/18-01-SUMMARY.md`
|
|
</output>
|