Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions cmd/axme/mesh.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,41 @@ import (
"context"
"fmt"
"os"
"strings"

"github.com/spf13/cobra"
)

const defaultMeshDashboardURL = "https://mesh.axme.ai"

// resolveDashboardURL picks the dashboard URL using this precedence:
// 1. explicit --dashboard-url flag (if non-empty and not equal to default)
// 2. AXME_MESH_DASHBOARD_URL env var
// 3. context-aware default: if gateway base_url looks like staging, the
// hardcoded prod URL will produce token mismatches — return empty string
// so the caller fails fast with a clear message
// 4. defaultMeshDashboardURL (prod)
func resolveDashboardURL(flagValue, gatewayBaseURL string) (string, error) {
if flagValue != "" && flagValue != defaultMeshDashboardURL {
return flagValue, nil
}
if envURL := strings.TrimSpace(os.Getenv("AXME_MESH_DASHBOARD_URL")); envURL != "" {
return envURL, nil
}
// Detect non-prod gateway: if the gateway is not api.cloud.axme.ai, the
// hardcoded prod dashboard URL will fail token exchange (token lives in a
// different environment's database). Refuse to open the wrong dashboard.
if gatewayBaseURL != "" && !strings.Contains(gatewayBaseURL, "api.cloud.axme.ai") {
return "", fmt.Errorf(
"current gateway is %q (non-prod). The default mesh dashboard at %s "+
"only works with the prod gateway. Set AXME_MESH_DASHBOARD_URL to a dashboard "+
"deployment connected to your gateway, or pass --dashboard-url",
gatewayBaseURL, defaultMeshDashboardURL,
)
}
return defaultMeshDashboardURL, nil
}

func newMeshCmd(rt *runtime) *cobra.Command {
cmd := &cobra.Command{
Use: "mesh",
Expand All @@ -30,13 +59,30 @@ func newMeshDashboardCmd(rt *runtime) *cobra.Command {

The command creates a one-time exchange token using your API key,
then opens the dashboard in your default browser. The token is
valid for 5 minutes and can only be used once.`,
valid for 5 minutes and can only be used once.

Dashboard URL precedence:
1. --dashboard-url flag
2. AXME_MESH_DASHBOARD_URL environment variable
3. https://mesh.axme.ai (default, prod-only)

Non-prod gateways (e.g. staging) will refuse to open the default URL because
the token would be created on the staging backend but the prod dashboard
would try to exchange it against the prod backend, producing
"Invalid exchange token". Use --dashboard-url or AXME_MESH_DASHBOARD_URL.`,
RunE: func(cmd *cobra.Command, args []string) error {
c := rt.effectiveContext()
if c.APIKey == "" {
return &cliError{Code: 2, Msg: "no API key configured. Run 'axme login' first."}
}

// Resolve dashboard URL BEFORE creating the token, so we fail fast
// when the gateway is non-prod and no override is provided.
resolvedDashboardURL, urlErr := resolveDashboardURL(dashboardURL, c.BaseURL)
if urlErr != nil {
return &cliError{Code: 2, Msg: urlErr.Error()}
}

// Create exchange token
fmt.Fprintf(os.Stderr, "Creating dashboard token...\n")
status, body, _, err := rt.doRequest(
Expand All @@ -60,7 +106,7 @@ valid for 5 minutes and can only be used once.`,
return &cliError{Code: 1, Msg: "server returned empty token"}
}

exchangeURL := fmt.Sprintf("%s/auth/exchange?token=%s", dashboardURL, token)
exchangeURL := fmt.Sprintf("%s/auth/exchange?token=%s", resolvedDashboardURL, token)

if rt.outputJSON {
rt.printJSON(map[string]any{
Expand Down
85 changes: 85 additions & 0 deletions cmd/axme/mesh_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package main

import (
"os"
"strings"
"testing"
)

// resolveDashboardURL precedence:
// 1. explicit --dashboard-url flag overrides everything
// 2. AXME_MESH_DASHBOARD_URL env var
// 3. context-aware default (refuse non-prod gateway)
// 4. defaultMeshDashboardURL (prod)

func TestResolveDashboardURL_FlagOverride(t *testing.T) {
t.Setenv("AXME_MESH_DASHBOARD_URL", "http://env-url.test")
got, err := resolveDashboardURL("https://flag-override.test", "https://api.cloud.axme.ai")
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if got != "https://flag-override.test" {
t.Errorf("expected flag override to win, got %s", got)
}
}

func TestResolveDashboardURL_FlagDefaultIgnored(t *testing.T) {
// If the user passes the default value, it should be treated as "no override"
// so env var/context-aware logic kicks in.
t.Setenv("AXME_MESH_DASHBOARD_URL", "http://env-url.test")
got, err := resolveDashboardURL(defaultMeshDashboardURL, "https://api.cloud.axme.ai")
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if got != "http://env-url.test" {
t.Errorf("expected env var to win when flag is default, got %s", got)
}
}

func TestResolveDashboardURL_EnvVar(t *testing.T) {
t.Setenv("AXME_MESH_DASHBOARD_URL", "http://env-url.test")
got, err := resolveDashboardURL("", "https://api.cloud.axme.ai")
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if got != "http://env-url.test" {
t.Errorf("expected env var, got %s", got)
}
}

func TestResolveDashboardURL_ProdDefault(t *testing.T) {
os.Unsetenv("AXME_MESH_DASHBOARD_URL")
got, err := resolveDashboardURL("", "https://api.cloud.axme.ai")
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if got != defaultMeshDashboardURL {
t.Errorf("expected %s, got %s", defaultMeshDashboardURL, got)
}
}

func TestResolveDashboardURL_StagingFailsFast(t *testing.T) {
os.Unsetenv("AXME_MESH_DASHBOARD_URL")
_, err := resolveDashboardURL("", "https://axme-gateway-staging-1047255398033.us-central1.run.app")
if err == nil {
t.Fatal("expected error on non-prod gateway")
}
if !strings.Contains(err.Error(), "non-prod") {
t.Errorf("expected 'non-prod' in error, got: %v", err)
}
if !strings.Contains(err.Error(), "AXME_MESH_DASHBOARD_URL") {
t.Errorf("expected env var hint in error, got: %v", err)
}
}

func TestResolveDashboardURL_EmptyGatewayBaseURL(t *testing.T) {
// Empty base URL (e.g. fresh config) should fall through to default
os.Unsetenv("AXME_MESH_DASHBOARD_URL")
got, err := resolveDashboardURL("", "")
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if got != defaultMeshDashboardURL {
t.Errorf("expected %s, got %s", defaultMeshDashboardURL, got)
}
}
Loading