summaryrefslogtreecommitdiff
path: root/cmd/dashboard/main.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-06-15 09:14:14 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-06-15 09:14:14 +0000
commitc86abd29845fa6feb61b1fddb8e8bb41312d55a8 (patch)
tree697fa3542035672a73ba9ddee8d47ca7044b0d5b /cmd/dashboard/main.go
parenteb089ef4c9416bd3d5642bb5a4437355ffa2fdd0 (diff)
feat: service gateway framework + playground demo
Formalizes doot as an authenticated reverse proxy for arbitrary upstream services. Any local HTTP service can now be registered behind doot's existing auth + SSL layer with 3 lines of config. - Rename NewClaudomatorProxy → NewServiceProxy (generic) - Replace hard-coded claudomator proxy block with serviceMount slice loop - Add PlaygroundURL config (PLAYGROUND_URL env var) - Add playground/web/server.py: stdlib Python status page on port 9090 - Document pattern in .agent/design.md; update .env.example To add a new service: set its URL env var, add a config field, append one mount entry in main.go. Claudomator's GitHub webhook bypass is expressed as webhookPaths on its mount. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'cmd/dashboard/main.go')
-rw-r--r--cmd/dashboard/main.go59
1 files changed, 44 insertions, 15 deletions
diff --git a/cmd/dashboard/main.go b/cmd/dashboard/main.go
index c648ede..596932a 100644
--- a/cmd/dashboard/main.go
+++ b/cmd/dashboard/main.go
@@ -163,8 +163,29 @@ func main() {
r.Use(appmiddleware.SecurityHeaders(cfg.Debug)) // Security headers
r.Use(sessionManager.LoadAndSave) // Session middleware must be applied globally
- // Initialize Claudomator reverse proxy
- claudomatorProxy := handlers.NewClaudomatorProxy(cfg.ClaudomatorURL, cfg.WebAuthnOrigin)
+ // Service gateway — upstream services proxied through doot's auth + SSL layer.
+ // Each mount registers a path prefix, upstream URL, and any public webhook paths.
+ type serviceMount struct {
+ name string
+ upstream string
+ prefix string
+ webhookPaths []string // POST paths that bypass session auth (e.g. HMAC-signed webhooks)
+ }
+ mounts := []serviceMount{
+ {
+ name: "claudomator",
+ upstream: cfg.ClaudomatorURL,
+ prefix: "/claudomator",
+ webhookPaths: []string{"/claudomator/api/webhooks/github"},
+ },
+ }
+ if cfg.PlaygroundURL != "" {
+ mounts = append(mounts, serviceMount{
+ name: "playground",
+ upstream: cfg.PlaygroundURL,
+ prefix: "/playground",
+ })
+ }
// Rate limiter for auth endpoints
authRateLimiter := appmiddleware.NewRateLimiter(config.AuthRateLimitRequests, config.AuthRateLimitWindow)
@@ -225,21 +246,29 @@ func main() {
})
})
- // Claudomator proxy routes
- // /claudomator (no trailing slash) -> 301 redirect
- r.Get("/claudomator", func(w http.ResponseWriter, r *http.Request) {
- http.Redirect(w, r, "/claudomator/", http.StatusMovedPermanently)
- })
+ // Register service gateway mounts — each upstream proxied behind doot's auth layer.
+ for _, m := range mounts {
+ proxy := handlers.NewServiceProxy(m.upstream, cfg.WebAuthnOrigin, m.prefix)
- // GitHub webhook: no auth (GitHub POSTs with HMAC, no session)
- r.Post("/claudomator/api/webhooks/github", claudomatorProxy.ServeHTTP)
+ // Bare prefix → redirect to prefix/
+ prefix := m.prefix
+ r.Get(prefix, func(w http.ResponseWriter, r *http.Request) {
+ http.Redirect(w, r, prefix+"/", http.StatusMovedPermanently)
+ })
+
+ // Public webhook paths bypass session auth (rely on service-level HMAC/token)
+ for _, wp := range m.webhookPaths {
+ r.Post(wp, proxy.ServeHTTP)
+ }
+
+ // All other routes require auth + origin check (CSRF protection for proxy)
+ r.Group(func(r chi.Router) {
+ r.Use(authHandlers.Middleware().RequireAuth)
+ r.Use(auth.OriginCheck(cfg.WebAuthnOrigin))
+ r.Handle(prefix+"/*", proxy)
+ })
+ }
- // All other Claudomator routes: RequireAuth + origin check (CSRF protection for proxy)
- r.Group(func(r chi.Router) {
- r.Use(authHandlers.Middleware().RequireAuth)
- r.Use(auth.OriginCheck(cfg.WebAuthnOrigin))
- r.Handle("/claudomator/*", claudomatorProxy)
- })
// Protected routes (auth required)
r.Group(func(r chi.Router) {