From 690ad73161b0f52d2d02073f4566c6b0079bb946 Mon Sep 17 00:00:00 2001 From: Forbes Date: Sat, 14 Feb 2026 14:02:11 -0600 Subject: [PATCH] feat(modules): public GET /api/modules discovery endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add HandleGetModules returning module state, metadata, and public config (auth providers, Create URI scheme). No auth required — clients call this pre-login. Register at /api/modules before the auth middleware. Ref #97 --- internal/api/handlers.go | 48 ++++++++++++++++++++++++++++++++++++++++ internal/api/routes.go | 1 + 2 files changed, 49 insertions(+) diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 3278953..b5afdb1 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -173,6 +173,54 @@ func (s *Server) HandleReady(w http.ResponseWriter, r *http.Request) { }) } +// HandleGetModules returns the public module discovery response. +// No authentication required — clients call this pre-login. +func (s *Server) HandleGetModules(w http.ResponseWriter, r *http.Request) { + mods := make(map[string]any, 10) + for _, m := range s.modules.All() { + entry := map[string]any{ + "enabled": s.modules.IsEnabled(m.ID), + "required": m.Required, + "name": m.Name, + } + if m.Version != "" { + entry["version"] = m.Version + } + if len(m.DependsOn) > 0 { + entry["depends_on"] = m.DependsOn + } + + // Public config (non-secret) for specific modules. + switch m.ID { + case "auth": + if s.cfg != nil { + entry["config"] = map[string]any{ + "local_enabled": s.cfg.Auth.Local.Enabled, + "ldap_enabled": s.cfg.Auth.LDAP.Enabled, + "oidc_enabled": s.cfg.Auth.OIDC.Enabled, + "oidc_issuer_url": s.cfg.Auth.OIDC.IssuerURL, + } + } + case "freecad": + if s.cfg != nil { + entry["config"] = map[string]any{ + "uri_scheme": s.cfg.FreeCAD.URIScheme, + } + } + } + + mods[m.ID] = entry + } + + writeJSON(w, http.StatusOK, map[string]any{ + "modules": mods, + "server": map[string]any{ + "version": "0.2", + "read_only": s.serverState.IsReadOnly(), + }, + }) +} + // Schema handlers // SchemaResponse represents a schema in API responses. diff --git a/internal/api/routes.go b/internal/api/routes.go index 49c6fc7..9829c6c 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -58,6 +58,7 @@ func NewRouter(server *Server, logger zerolog.Logger) http.Handler { r.Get("/auth/callback", server.HandleOIDCCallback) // Public API endpoints (no auth required) + r.Get("/api/modules", server.HandleGetModules) r.Get("/api/auth/config", server.HandleAuthConfig) // API routes (require auth, no CSRF — token auth instead)