diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-06-15 09:14:14 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-06-15 09:14:14 +0000 |
| commit | c86abd29845fa6feb61b1fddb8e8bb41312d55a8 (patch) | |
| tree | 697fa3542035672a73ba9ddee8d47ca7044b0d5b /cmd | |
| parent | eb089ef4c9416bd3d5642bb5a4437355ffa2fdd0 (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')
| -rw-r--r-- | cmd/dashboard/main.go | 59 |
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) { |
