diff --git a/pkg/cli/cmd/namespacecmd/namespace.go b/pkg/cli/cmd/namespacecmd/namespace.go index 1807f74..db0d0e9 100644 --- a/pkg/cli/cmd/namespacecmd/namespace.go +++ b/pkg/cli/cmd/namespacecmd/namespace.go @@ -45,10 +45,59 @@ var repairCmd = &cobra.Command{ }, } +var enableCmd = &cobra.Command{ + Use: "enable ", + Short: "Enable a feature for a namespace", + Long: "Enable a feature for a namespace. Supported features: webrtc", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + ns, _ := cmd.Flags().GetString("namespace") + cliArgs := []string{"enable", args[0]} + if ns != "" { + cliArgs = append(cliArgs, "--namespace", ns) + } + cli.HandleNamespaceCommand(cliArgs) + }, +} + +var disableCmd = &cobra.Command{ + Use: "disable ", + Short: "Disable a feature for a namespace", + Long: "Disable a feature for a namespace. Supported features: webrtc", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + ns, _ := cmd.Flags().GetString("namespace") + cliArgs := []string{"disable", args[0]} + if ns != "" { + cliArgs = append(cliArgs, "--namespace", ns) + } + cli.HandleNamespaceCommand(cliArgs) + }, +} + +var webrtcStatusCmd = &cobra.Command{ + Use: "webrtc-status", + Short: "Show WebRTC service status for a namespace", + Run: func(cmd *cobra.Command, args []string) { + ns, _ := cmd.Flags().GetString("namespace") + cliArgs := []string{"webrtc-status"} + if ns != "" { + cliArgs = append(cliArgs, "--namespace", ns) + } + cli.HandleNamespaceCommand(cliArgs) + }, +} + func init() { deleteCmd.Flags().Bool("force", false, "Skip confirmation prompt") + enableCmd.Flags().String("namespace", "", "Namespace name") + disableCmd.Flags().String("namespace", "", "Namespace name") + webrtcStatusCmd.Flags().String("namespace", "", "Namespace name") Cmd.AddCommand(listCmd) Cmd.AddCommand(deleteCmd) Cmd.AddCommand(repairCmd) + Cmd.AddCommand(enableCmd) + Cmd.AddCommand(disableCmd) + Cmd.AddCommand(webrtcStatusCmd) } diff --git a/pkg/cli/namespace_commands.go b/pkg/cli/namespace_commands.go index ad37591..6150406 100644 --- a/pkg/cli/namespace_commands.go +++ b/pkg/cli/namespace_commands.go @@ -241,21 +241,27 @@ func handleNamespaceEnable(args []string) { os.Exit(1) } + gatewayURL, apiKey := loadAuthForNamespace(ns) + fmt.Printf("Enabling WebRTC for namespace '%s'...\n", ns) fmt.Printf("This will provision SFU (3 nodes) and TURN (2 nodes) services.\n") - url := fmt.Sprintf("http://localhost:%d/v1/internal/namespace/webrtc/enable?namespace=%s", constants.GatewayAPIPort, ns) + url := fmt.Sprintf("%s/v1/namespace/webrtc/enable", gatewayURL) req, err := http.NewRequest(http.MethodPost, url, nil) if err != nil { fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err) os.Exit(1) } - req.Header.Set("X-Orama-Internal-Auth", "namespace-coordination") + req.Header.Set("Authorization", "Bearer "+apiKey) - client := &http.Client{} + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } resp, err := client.Do(req) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to connect to local gateway (is the node running?): %v\n", err) + fmt.Fprintf(os.Stderr, "Failed to connect to gateway: %v\n", err) os.Exit(1) } defer resp.Body.Close() @@ -294,20 +300,26 @@ func handleNamespaceDisable(args []string) { os.Exit(1) } + gatewayURL, apiKey := loadAuthForNamespace(ns) + fmt.Printf("Disabling WebRTC for namespace '%s'...\n", ns) - url := fmt.Sprintf("http://localhost:%d/v1/internal/namespace/webrtc/disable?namespace=%s", constants.GatewayAPIPort, ns) + url := fmt.Sprintf("%s/v1/namespace/webrtc/disable", gatewayURL) req, err := http.NewRequest(http.MethodPost, url, nil) if err != nil { fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err) os.Exit(1) } - req.Header.Set("X-Orama-Internal-Auth", "namespace-coordination") + req.Header.Set("Authorization", "Bearer "+apiKey) - client := &http.Client{} + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } resp, err := client.Do(req) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to connect to local gateway (is the node running?): %v\n", err) + fmt.Fprintf(os.Stderr, "Failed to connect to gateway: %v\n", err) os.Exit(1) } defer resp.Body.Close() @@ -329,18 +341,24 @@ func handleNamespaceDisable(args []string) { } func handleNamespaceWebRTCStatus(ns string) { - url := fmt.Sprintf("http://localhost:%d/v1/internal/namespace/webrtc/status?namespace=%s", constants.GatewayAPIPort, ns) + gatewayURL, apiKey := loadAuthForNamespace(ns) + + url := fmt.Sprintf("%s/v1/namespace/webrtc/status", gatewayURL) req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err) os.Exit(1) } - req.Header.Set("X-Orama-Internal-Auth", "namespace-coordination") + req.Header.Set("Authorization", "Bearer "+apiKey) - client := &http.Client{} + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } resp, err := client.Do(req) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to connect to local gateway (is the node running?): %v\n", err) + fmt.Fprintf(os.Stderr, "Failed to connect to gateway: %v\n", err) os.Exit(1) } defer resp.Body.Close() @@ -383,6 +401,26 @@ func handleNamespaceWebRTCStatus(ns string) { } } +// loadAuthForNamespace loads credentials and returns the gateway URL and API key. +// Exits with an error message if not authenticated. +func loadAuthForNamespace(ns string) (gatewayURL, apiKey string) { + store, err := auth.LoadEnhancedCredentials() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to load credentials: %v\n", err) + os.Exit(1) + } + + gatewayURL = getGatewayURL() + creds := store.GetDefaultCredential(gatewayURL) + + if creds == nil || !creds.IsValid() { + fmt.Fprintf(os.Stderr, "Not authenticated. Run 'orama auth login' first.\n") + os.Exit(1) + } + + return gatewayURL, creds.APIKey +} + func handleNamespaceList() { // Load credentials store, err := auth.LoadEnhancedCredentials() diff --git a/pkg/gateway/gateway.go b/pkg/gateway/gateway.go index 6c0c2b4..7c2f703 100644 --- a/pkg/gateway/gateway.go +++ b/pkg/gateway/gateway.go @@ -871,6 +871,109 @@ func (g *Gateway) namespaceClusterRepairHandler(w http.ResponseWriter, r *http.R }) } +// namespaceWebRTCEnablePublicHandler handles POST /v1/namespace/webrtc/enable +// Public: authenticated by JWT/API key via auth middleware. Namespace from context. +func (g *Gateway) namespaceWebRTCEnablePublicHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + writeError(w, http.StatusMethodNotAllowed, "method not allowed") + return + } + + namespaceName, _ := r.Context().Value(CtxKeyNamespaceOverride).(string) + if namespaceName == "" { + writeError(w, http.StatusForbidden, "namespace not resolved") + return + } + + if g.webrtcManager == nil { + writeError(w, http.StatusServiceUnavailable, "WebRTC management not enabled") + return + } + + if err := g.webrtcManager.EnableWebRTC(r.Context(), namespaceName, "api"); err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "status": "ok", + "namespace": namespaceName, + "message": "WebRTC enabled successfully", + }) +} + +// namespaceWebRTCDisablePublicHandler handles POST /v1/namespace/webrtc/disable +// Public: authenticated by JWT/API key via auth middleware. Namespace from context. +func (g *Gateway) namespaceWebRTCDisablePublicHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + writeError(w, http.StatusMethodNotAllowed, "method not allowed") + return + } + + namespaceName, _ := r.Context().Value(CtxKeyNamespaceOverride).(string) + if namespaceName == "" { + writeError(w, http.StatusForbidden, "namespace not resolved") + return + } + + if g.webrtcManager == nil { + writeError(w, http.StatusServiceUnavailable, "WebRTC management not enabled") + return + } + + if err := g.webrtcManager.DisableWebRTC(r.Context(), namespaceName); err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "status": "ok", + "namespace": namespaceName, + "message": "WebRTC disabled successfully", + }) +} + +// namespaceWebRTCStatusPublicHandler handles GET /v1/namespace/webrtc/status +// Public: authenticated by JWT/API key via auth middleware. Namespace from context. +func (g *Gateway) namespaceWebRTCStatusPublicHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + writeError(w, http.StatusMethodNotAllowed, "method not allowed") + return + } + + namespaceName, _ := r.Context().Value(CtxKeyNamespaceOverride).(string) + if namespaceName == "" { + writeError(w, http.StatusForbidden, "namespace not resolved") + return + } + + if g.webrtcManager == nil { + writeError(w, http.StatusServiceUnavailable, "WebRTC management not enabled") + return + } + + config, err := g.webrtcManager.GetWebRTCStatus(r.Context(), namespaceName) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if config == nil { + json.NewEncoder(w).Encode(map[string]interface{}{ + "namespace": namespaceName, + "enabled": false, + }) + } else { + json.NewEncoder(w).Encode(config) + } +} + // namespaceWebRTCEnableHandler handles POST /v1/internal/namespace/webrtc/enable?namespace={name} // Internal-only: authenticated by X-Orama-Internal-Auth header + WireGuard subnet. func (g *Gateway) namespaceWebRTCEnableHandler(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/gateway/routes.go b/pkg/gateway/routes.go index fa97577..4e49910 100644 --- a/pkg/gateway/routes.go +++ b/pkg/gateway/routes.go @@ -52,6 +52,11 @@ func (g *Gateway) Routes() http.Handler { mux.HandleFunc("/v1/internal/namespace/webrtc/disable", g.namespaceWebRTCDisableHandler) mux.HandleFunc("/v1/internal/namespace/webrtc/status", g.namespaceWebRTCStatusHandler) + // Namespace WebRTC enable/disable/status (public, JWT/API key auth via middleware) + mux.HandleFunc("/v1/namespace/webrtc/enable", g.namespaceWebRTCEnablePublicHandler) + mux.HandleFunc("/v1/namespace/webrtc/disable", g.namespaceWebRTCDisablePublicHandler) + mux.HandleFunc("/v1/namespace/webrtc/status", g.namespaceWebRTCStatusPublicHandler) + // auth endpoints mux.HandleFunc("/v1/auth/jwks", g.authService.JWKSHandler) mux.HandleFunc("/.well-known/jwks.json", g.authService.JWKSHandler)