| 112 | } |
| 113 | |
| 114 | func (s *Server) Router() http.Handler { |
| 115 | inner := chi.NewRouter() |
| 116 | inner.Route("/api", func(r chi.Router) { |
| 117 | r.Get("/healthz", s.healthz) |
| 118 | r.Get("/version", s.version) |
| 119 | r.Get("/capabilities", s.capabilities) |
| 120 | r.Get("/platform", s.platform) |
| 121 | r.Get("/session", s.sessionStatus) |
| 122 | r.Post("/session/login", s.sessionLogin) |
| 123 | r.Post("/session/logout", s.sessionLogout) |
| 124 | |
| 125 | r.Group(func(r chi.Router) { |
| 126 | r.Use(s.requireAuth) |
| 127 | s.registerProtectedRoutes(r) |
| 128 | }) |
| 129 | }) |
| 130 | |
| 131 | // Legacy root-level API compatibility. Keep this around so the web client |
| 132 | // still works during partial restarts or when an older bundle is cached. |
| 133 | inner.Get("/healthz", s.healthz) |
| 134 | inner.Get("/version", s.version) |
| 135 | inner.Get("/capabilities", s.capabilities) |
| 136 | inner.Get("/platform", s.platform) |
| 137 | inner.Get("/session", s.sessionStatus) |
| 138 | inner.Post("/session/login", s.sessionLogin) |
| 139 | inner.Post("/session/logout", s.sessionLogout) |
| 140 | inner.Group(func(r chi.Router) { |
| 141 | r.Use(s.requireAuth) |
| 142 | s.registerProtectedRoutes(r) |
| 143 | }) |
| 144 | |
| 145 | // Static / PWA fallback. |
| 146 | if s.Static != nil { |
| 147 | inner.Get("/*", s.serveStatic) |
| 148 | } |
| 149 | |
| 150 | outer := chi.NewRouter() |
| 151 | outer.Use(middleware.RequestID) |
| 152 | // Intentionally not using middleware.RealIP: it rewrites |
| 153 | // r.RemoteAddr from X-Forwarded-For unconditionally, which would |
| 154 | // let any client spoof the rate-limit and audit identity. |
| 155 | // clientAddressKey() does trust-aware extraction instead. |
| 156 | outer.Use(s.securityHeadersMiddleware) |
| 157 | outer.Use(s.corsMiddleware) |
| 158 | outer.Use(middleware.Recoverer) |
| 159 | |
| 160 | basePath := s.currentConfig().BasePath |
| 161 | if basePath != "" { |
| 162 | outer.Mount(basePath, inner) |
| 163 | return outer |
| 164 | } |
| 165 | outer.Mount("/", inner) |
| 166 | return outer |
| 167 | } |
| 168 | |
| 169 | func (s *Server) registerProtectedRoutes(r chi.Router) { |
| 170 | r.Post("/session/rotate-token", s.sessionRotateToken) |