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)