From 1fca8cb4113a4563daa70d1ccc62851a93b3f9bf Mon Sep 17 00:00:00 2001 From: anonpenguin Date: Wed, 20 Aug 2025 12:51:54 +0300 Subject: [PATCH] Add authentication to protected CLI commands This commit adds wallet-based authentication to protected CLI commands by removing the manual auth command and automatically prompting for credentials when needed. Protected commands will check for valid credentials and trigger the auth --- cmd/cli/main.go | 195 +++-------------- pkg/auth/enhanced_auth.go | 395 +++++++++++++++++++++++++++++++++++ pkg/gateway/auth_handlers.go | 59 ++++-- pkg/gateway/db_helpers.go | 10 +- 4 files changed, 469 insertions(+), 190 deletions(-) create mode 100644 pkg/auth/enhanced_auth.go diff --git a/cmd/cli/main.go b/cmd/cli/main.go index b4b750a..96fd9a0 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -6,8 +6,6 @@ import ( "encoding/json" "fmt" "log" - "net" - "net/http" "os" "os/exec" "strconv" @@ -88,8 +86,7 @@ func main() { handlePeerID() case "help", "--help", "-h": showHelp() - case "auth": - handleAuth(args) + default: fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command) showHelp() @@ -192,6 +189,9 @@ func handleStatus() { } func handleQuery(sql string) { + // Ensure user is authenticated + _ = ensureAuthenticated() + client, err := createClient() if err != nil { fmt.Fprintf(os.Stderr, "Failed to create client: %v\n", err) @@ -221,6 +221,9 @@ func handleStorage(args []string) { os.Exit(1) } + // Ensure user is authenticated + _ = ensureAuthenticated() + client, err := createClient() if err != nil { fmt.Fprintf(os.Stderr, "Failed to create client: %v\n", err) @@ -290,6 +293,9 @@ func handlePubSub(args []string) { os.Exit(1) } + // Ensure user is authenticated + _ = ensureAuthenticated() + client, err := createClient() if err != nil { fmt.Fprintf(os.Stderr, "Failed to create client: %v\n", err) @@ -365,164 +371,18 @@ func handlePubSub(args []string) { } } -// handleAuth launches a local webpage to perform wallet signature and obtain an API key. -// Usage: network-cli auth [--gateway ] [--namespace ] [--wallet ] [--plan ] -func handleAuth(args []string) { - // Defaults - gatewayURL := getenvDefault("GATEWAY_URL", "http://localhost:8080") - namespace := getenvDefault("GATEWAY_NAMESPACE", "default") - wallet := "" - plan := "free" +// ensureAuthenticated ensures the user has valid credentials for the gateway +// Returns the credentials or exits the program on failure +func ensureAuthenticated() *auth.Credentials { + gatewayURL := auth.GetDefaultGatewayURL() - // Parse simple flags - for i := 0; i < len(args); i++ { - switch args[i] { - case "--gateway": - if i+1 < len(args) { - gatewayURL = strings.TrimSpace(args[i+1]) - i++ - } - case "--namespace": - if i+1 < len(args) { - namespace = strings.TrimSpace(args[i+1]) - i++ - } - case "--wallet": - if i+1 < len(args) { - wallet = strings.TrimSpace(args[i+1]) - i++ - } - case "--plan": - if i+1 < len(args) { - plan = strings.TrimSpace(strings.ToLower(args[i+1])) - i++ - } - } - } - - // Spin up local HTTP server on random port - ln, err := net.Listen("tcp", "localhost:0") + credentials, err := auth.GetOrPromptForCredentials(gatewayURL) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to listen: %v\n", err) + fmt.Fprintf(os.Stderr, "Authentication failed: %v\n", err) os.Exit(1) } - defer ln.Close() - addr := ln.Addr().String() - // Normalize URL host to localhost for consistency with gateway default - parts := strings.Split(addr, ":") - listenURL := "http://localhost:" + parts[len(parts)-1] + "/" - // Channel to receive API key - type result struct { - APIKey string `json:"api_key"` - Namespace string `json:"namespace"` - } - resCh := make(chan result, 1) - srv := &http.Server{} - - mux := http.NewServeMux() - // Root serves the HTML page with embedded gateway URL and defaults - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - fmt.Fprintf(w, ` - -DeBros Auth - - - -

Authenticate with Wallet to Get API Key

-

This will create or return an API key for namespace on gateway .

-
-
-
-
- - -

-
-`, gatewayURL, namespace, wallet, plan)
-	})
-	// Callback to deliver API key back to CLI
-	mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
-		if r.Method != http.MethodPost {
-			w.WriteHeader(http.StatusMethodNotAllowed)
-			return
-		}
-		var payload struct {
-			APIKey    string `json:"api_key"`
-			Namespace string `json:"namespace"`
-		}
-		if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
-			w.WriteHeader(http.StatusBadRequest)
-			return
-		}
-		if strings.TrimSpace(payload.APIKey) == "" {
-			w.WriteHeader(http.StatusBadRequest)
-			return
-		}
-		select {
-		case resCh <- result{APIKey: payload.APIKey, Namespace: payload.Namespace}:
-		default:
-		}
-		_, _ = w.Write([]byte("ok"))
-		go func() { time.Sleep(500 * time.Millisecond); _ = srv.Close() }()
-	})
-	srv.Handler = mux
-
-	// Open browser
-	url := listenURL
-	go func() {
-		// Try to open in default browser
-		_ = openBrowser(url)
-	}()
-
-	// Serve and wait for result or timeout
-	go func() { _ = srv.Serve(ln) }()
-	fmt.Printf("🌐 Please complete authentication in your browser: %s\n", url)
-	select {
-	case r := <-resCh:
-		fmt.Printf("āœ… API Key issued for namespace '%s'\n", r.Namespace)
-		fmt.Printf("%s\n", r.APIKey)
-	case <-time.After(5 * time.Minute):
-		fmt.Fprintf(os.Stderr, "Timed out waiting for wallet signature.\n")
-		_ = srv.Close()
-		os.Exit(1)
-	}
+	return credentials
 }
 
 func openBrowser(target string) error {
@@ -737,26 +597,31 @@ func isPrintableText(s string) bool {
 func showHelp() {
 	fmt.Printf("Network CLI - Distributed P2P Network Management Tool\n\n")
 	fmt.Printf("Usage: network-cli  [args...]\n\n")
+	fmt.Printf("šŸ” Authentication: Commands requiring authentication will automatically prompt for wallet connection.\n\n")
 	fmt.Printf("Commands:\n")
 	fmt.Printf("  health                    - Check network health\n")
 	fmt.Printf("  peers                     - List connected peers\n")
 	fmt.Printf("  status                    - Show network status\n")
 	fmt.Printf("  peer-id                   - Show this node's peer ID\n")
-	fmt.Printf("  query                - Execute database query\n")
-	fmt.Printf("  storage get          - Get value from storage\n")
-	fmt.Printf("  storage put   - Store value in storage\n")
-	fmt.Printf("  storage list [prefix]     - List storage keys\n")
-	fmt.Printf("  pubsub publish   - Publish message\n")
-	fmt.Printf("  pubsub subscribe  [duration] - Subscribe to topic\n")
-	fmt.Printf("  pubsub topics             - List topics\n")
+	fmt.Printf("  query                šŸ” Execute database query\n")
+	fmt.Printf("  storage get          šŸ” Get value from storage\n")
+	fmt.Printf("  storage put   šŸ” Store value in storage\n")
+	fmt.Printf("  storage list [prefix]     šŸ” List storage keys\n")
+	fmt.Printf("  pubsub publish   šŸ” Publish message\n")
+	fmt.Printf("  pubsub subscribe  [duration] šŸ” Subscribe to topic\n")
+	fmt.Printf("  pubsub topics             šŸ” List topics\n")
 	fmt.Printf("  connect     - Connect to peer\n")
-	fmt.Printf("  auth [--gateway URL] [--namespace NS] [--wallet 0x..] [--plan free|premium] - Obtain API key via wallet signature\n")
+
 	fmt.Printf("  help                      - Show this help\n\n")
 	fmt.Printf("Global Flags:\n")
 	fmt.Printf("  -b, --bootstrap     - Bootstrap peer address (default: /ip4/127.0.0.1/tcp/4001)\n")
 	fmt.Printf("  -f, --format      - Output format: table, json (default: table)\n")
 	fmt.Printf("  -t, --timeout   - Operation timeout (default: 30s)\n")
 	fmt.Printf("  --production              - Connect to production bootstrap peers\n\n")
+	fmt.Printf("Authentication:\n")
+	fmt.Printf("  Commands marked with šŸ” will automatically prompt for wallet authentication\n")
+	fmt.Printf("  if no valid credentials are found. You can manage multiple wallets and\n")
+	fmt.Printf("  choose between them during the authentication flow.\n\n")
 	fmt.Printf("Examples:\n")
 	fmt.Printf("  network-cli health\n")
 	fmt.Printf("  network-cli peer-id\n")
diff --git a/pkg/auth/enhanced_auth.go b/pkg/auth/enhanced_auth.go
new file mode 100644
index 0000000..412364a
--- /dev/null
+++ b/pkg/auth/enhanced_auth.go
@@ -0,0 +1,395 @@
+package auth
+
+import (
+	"bufio"
+	"encoding/json"
+	"fmt"
+	"os"
+	"strconv"
+	"strings"
+)
+
+// EnhancedCredentialStore manages multiple credentials per gateway
+type EnhancedCredentialStore struct {
+	Gateways map[string]*GatewayCredentials `json:"gateways"`
+	Version  string                         `json:"version"`
+}
+
+// GatewayCredentials holds multiple credentials for a single gateway
+type GatewayCredentials struct {
+	Credentials   []*Credentials `json:"credentials"`
+	DefaultIndex  int            `json:"default_index"`
+	LastUsedIndex int            `json:"last_used_index"`
+}
+
+// AuthChoice represents user's choice during authentication
+type AuthChoice int
+
+const (
+	AuthChoiceUseCredential AuthChoice = iota
+	AuthChoiceAddCredential
+	AuthChoiceLogout
+	AuthChoiceExit
+)
+
+// LoadEnhancedCredentials loads the enhanced credential store
+func LoadEnhancedCredentials() (*EnhancedCredentialStore, error) {
+	credPath, err := GetCredentialsPath()
+	if err != nil {
+		return nil, err
+	}
+
+	// If file doesn't exist, return empty store
+	if _, err := os.Stat(credPath); os.IsNotExist(err) {
+		return &EnhancedCredentialStore{
+			Gateways: make(map[string]*GatewayCredentials),
+			Version:  "2.0",
+		}, nil
+	}
+
+	data, err := os.ReadFile(credPath)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read credentials file: %w", err)
+	}
+
+	// Try to parse as enhanced store first
+	var enhancedStore EnhancedCredentialStore
+	if err := json.Unmarshal(data, &enhancedStore); err == nil && enhancedStore.Version == "2.0" {
+		// Initialize maps if nil
+		if enhancedStore.Gateways == nil {
+			enhancedStore.Gateways = make(map[string]*GatewayCredentials)
+		}
+		return &enhancedStore, nil
+	}
+
+	// Fall back to old format and migrate
+	var oldStore CredentialStore
+	if err := json.Unmarshal(data, &oldStore); err != nil {
+		return nil, fmt.Errorf("failed to parse credentials file: %w", err)
+	}
+
+	// Migrate old format to new
+	enhancedStore = EnhancedCredentialStore{
+		Gateways: make(map[string]*GatewayCredentials),
+		Version:  "2.0",
+	}
+
+	for gatewayURL, creds := range oldStore.Gateways {
+		if creds != nil {
+			enhancedStore.Gateways[gatewayURL] = &GatewayCredentials{
+				Credentials:   []*Credentials{creds},
+				DefaultIndex:  0,
+				LastUsedIndex: 0,
+			}
+		}
+	}
+
+	return &enhancedStore, nil
+}
+
+// Save saves the enhanced credential store
+func (store *EnhancedCredentialStore) Save() error {
+	credPath, err := GetCredentialsPath()
+	if err != nil {
+		return err
+	}
+
+	if store.Version == "" {
+		store.Version = "2.0"
+	}
+
+	data, err := json.MarshalIndent(store, "", "  ")
+	if err != nil {
+		return fmt.Errorf("failed to marshal credentials: %w", err)
+	}
+
+	return os.WriteFile(credPath, data, 0600)
+}
+
+// AddCredential adds a new credential for the gateway
+func (store *EnhancedCredentialStore) AddCredential(gatewayURL string, creds *Credentials) {
+	if store.Gateways == nil {
+		store.Gateways = make(map[string]*GatewayCredentials)
+	}
+
+	gatewayCredentials := store.Gateways[gatewayURL]
+	if gatewayCredentials == nil {
+		gatewayCredentials = &GatewayCredentials{
+			Credentials:   []*Credentials{},
+			DefaultIndex:  0,
+			LastUsedIndex: 0,
+		}
+		store.Gateways[gatewayURL] = gatewayCredentials
+	}
+
+	// Check if credential already exists (by wallet address)
+	for i, existing := range gatewayCredentials.Credentials {
+		if strings.EqualFold(existing.Wallet, creds.Wallet) {
+			// Update existing credential
+			gatewayCredentials.Credentials[i] = creds
+			return
+		}
+	}
+
+	// Add new credential
+	gatewayCredentials.Credentials = append(gatewayCredentials.Credentials, creds)
+}
+
+// GetDefaultCredential returns the default credential for a gateway
+func (store *EnhancedCredentialStore) GetDefaultCredential(gatewayURL string) *Credentials {
+	gatewayCredentials := store.Gateways[gatewayURL]
+	if gatewayCredentials == nil || len(gatewayCredentials.Credentials) == 0 {
+		return nil
+	}
+
+	// Ensure default index is valid
+	if gatewayCredentials.DefaultIndex < 0 || gatewayCredentials.DefaultIndex >= len(gatewayCredentials.Credentials) {
+		gatewayCredentials.DefaultIndex = 0
+	}
+
+	return gatewayCredentials.Credentials[gatewayCredentials.DefaultIndex]
+}
+
+// SetDefaultCredential sets the default credential by index
+func (store *EnhancedCredentialStore) SetDefaultCredential(gatewayURL string, index int) bool {
+	gatewayCredentials := store.Gateways[gatewayURL]
+	if gatewayCredentials == nil || index < 0 || index >= len(gatewayCredentials.Credentials) {
+		return false
+	}
+
+	gatewayCredentials.DefaultIndex = index
+	gatewayCredentials.LastUsedIndex = index
+	return true
+}
+
+// ClearAllCredentials removes all credentials
+func (store *EnhancedCredentialStore) ClearAllCredentials() {
+	store.Gateways = make(map[string]*GatewayCredentials)
+}
+
+// DisplayCredentialMenu shows the interactive credential selection menu
+func (store *EnhancedCredentialStore) DisplayCredentialMenu(gatewayURL string) (AuthChoice, int, error) {
+	gatewayCredentials := store.Gateways[gatewayURL]
+
+	if gatewayCredentials == nil || len(gatewayCredentials.Credentials) == 0 {
+		fmt.Println("\nšŸ” No credentials found. Choose an option:")
+		fmt.Println("1. Authenticate with new wallet")
+		fmt.Println("2. Exit")
+		fmt.Print("Choose (1-2): ")
+
+		choice, err := readUserChoice(2)
+		if err != nil {
+			return AuthChoiceExit, -1, err
+		}
+
+		switch choice {
+		case 1:
+			return AuthChoiceAddCredential, -1, nil
+		case 2:
+			return AuthChoiceExit, -1, nil
+		default:
+			return AuthChoiceExit, -1, fmt.Errorf("invalid choice")
+		}
+	}
+
+	fmt.Printf("\nšŸ” Multiple wallets available for %s:\n", gatewayURL)
+
+	// Display credentials
+	for i, creds := range gatewayCredentials.Credentials {
+		defaultMark := ""
+		if i == gatewayCredentials.DefaultIndex {
+			defaultMark = " (default)"
+		}
+
+		// Format wallet address for display
+		displayAddr := creds.Wallet
+		if len(displayAddr) > 10 {
+			displayAddr = displayAddr[:6] + "..." + displayAddr[len(displayAddr)-4:]
+		}
+
+		statusEmoji := "āœ…"
+		if !creds.IsValid() {
+			statusEmoji = "āŒ"
+		}
+
+		planInfo := ""
+		if creds.Plan != "" {
+			planInfo = fmt.Sprintf(" (%s)", creds.Plan)
+		}
+
+		fmt.Printf("%d. %s %s%s%s\n", i+1, statusEmoji, displayAddr, planInfo, defaultMark)
+	}
+
+	fmt.Printf("%d. Add new wallet\n", len(gatewayCredentials.Credentials)+1)
+	fmt.Printf("%d. Logout (clear all credentials)\n", len(gatewayCredentials.Credentials)+2)
+	fmt.Printf("%d. Exit\n", len(gatewayCredentials.Credentials)+3)
+
+	maxChoice := len(gatewayCredentials.Credentials) + 3
+	fmt.Printf("Choose (1-%d): ", maxChoice)
+
+	choice, err := readUserChoice(maxChoice)
+	if err != nil {
+		return AuthChoiceExit, -1, err
+	}
+
+	if choice <= len(gatewayCredentials.Credentials) {
+		// User selected a credential
+		return AuthChoiceUseCredential, choice - 1, nil
+	} else if choice == len(gatewayCredentials.Credentials)+1 {
+		// Add new credential
+		return AuthChoiceAddCredential, -1, nil
+	} else if choice == len(gatewayCredentials.Credentials)+2 {
+		// Logout
+		return AuthChoiceLogout, -1, nil
+	} else {
+		// Exit
+		return AuthChoiceExit, -1, nil
+	}
+}
+
+// readUserChoice reads and validates user input
+func readUserChoice(maxChoice int) (int, error) {
+	reader := bufio.NewReader(os.Stdin)
+	input, err := reader.ReadString('\n')
+	if err != nil {
+		return 0, fmt.Errorf("failed to read input: %w", err)
+	}
+
+	choiceStr := strings.TrimSpace(input)
+	choice, err := strconv.Atoi(choiceStr)
+	if err != nil {
+		return 0, fmt.Errorf("invalid input: please enter a number")
+	}
+
+	if choice < 1 || choice > maxChoice {
+		return 0, fmt.Errorf("invalid choice: please enter a number between 1 and %d", maxChoice)
+	}
+
+	return choice, nil
+}
+
+// GetOrPromptForCredentials handles the complete authentication flow
+func GetOrPromptForCredentials(gatewayURL string) (*Credentials, error) {
+	store, err := LoadEnhancedCredentials()
+	if err != nil {
+		return nil, fmt.Errorf("failed to load credential store: %w", err)
+	}
+
+	// Check if we have a valid default credential
+	defaultCreds := store.GetDefaultCredential(gatewayURL)
+	if defaultCreds != nil && defaultCreds.IsValid() {
+		// Update last used time
+		defaultCreds.UpdateLastUsed()
+		if err := store.Save(); err != nil {
+			// Log warning but don't fail
+			fmt.Fprintf(os.Stderr, "Warning: failed to update last used time: %v\n", err)
+		}
+		return defaultCreds, nil
+	}
+
+	// Need to prompt user for credential selection
+	for {
+		choice, credIndex, err := store.DisplayCredentialMenu(gatewayURL)
+		if err != nil {
+			return nil, fmt.Errorf("menu selection failed: %w", err)
+		}
+
+		switch choice {
+		case AuthChoiceUseCredential:
+			gatewayCredentials := store.Gateways[gatewayURL]
+			if gatewayCredentials == nil || credIndex < 0 || credIndex >= len(gatewayCredentials.Credentials) {
+				fmt.Println("āŒ Invalid credential selection")
+				continue
+			}
+
+			selectedCreds := gatewayCredentials.Credentials[credIndex]
+			if !selectedCreds.IsValid() {
+				fmt.Println("āŒ Selected credentials are invalid or expired")
+				continue
+			}
+
+			// Update default and last used
+			store.SetDefaultCredential(gatewayURL, credIndex)
+			selectedCreds.UpdateLastUsed()
+
+			if err := store.Save(); err != nil {
+				fmt.Fprintf(os.Stderr, "Warning: failed to save credentials: %v\n", err)
+			}
+
+			return selectedCreds, nil
+
+		case AuthChoiceAddCredential:
+			fmt.Println("\n🌐 Opening browser for wallet authentication...")
+			newCreds, err := PerformWalletAuthentication(gatewayURL)
+			if err != nil {
+				fmt.Printf("āŒ Authentication failed: %v\n", err)
+				continue
+			}
+
+			// Add the new credential
+			store.AddCredential(gatewayURL, newCreds)
+
+			// Set as default if it's the first credential
+			gatewayCredentials := store.Gateways[gatewayURL]
+			if gatewayCredentials != nil && len(gatewayCredentials.Credentials) == 1 {
+				store.SetDefaultCredential(gatewayURL, 0)
+			}
+
+			if err := store.Save(); err != nil {
+				return nil, fmt.Errorf("failed to save new credentials: %w", err)
+			}
+
+			fmt.Printf("āœ… Wallet %s added successfully\n", newCreds.Wallet)
+			return newCreds, nil
+
+		case AuthChoiceLogout:
+			store.ClearAllCredentials()
+			if err := store.Save(); err != nil {
+				return nil, fmt.Errorf("failed to clear credentials: %w", err)
+			}
+			fmt.Println("āœ… All credentials cleared")
+			continue
+
+		case AuthChoiceExit:
+			return nil, fmt.Errorf("authentication cancelled by user")
+
+		default:
+			fmt.Println("āŒ Invalid choice")
+			continue
+		}
+	}
+}
+
+// HasValidEnhancedCredentials checks if there are valid credentials for the default gateway
+func HasValidEnhancedCredentials() (bool, error) {
+	store, err := LoadEnhancedCredentials()
+	if err != nil {
+		return false, err
+	}
+
+	gatewayURL := GetDefaultGatewayURL()
+	defaultCreds := store.GetDefaultCredential(gatewayURL)
+
+	return defaultCreds != nil && defaultCreds.IsValid(), nil
+}
+
+// GetValidEnhancedCredentials returns valid credentials for the default gateway
+func GetValidEnhancedCredentials() (*Credentials, error) {
+	store, err := LoadEnhancedCredentials()
+	if err != nil {
+		return nil, err
+	}
+
+	gatewayURL := GetDefaultGatewayURL()
+	defaultCreds := store.GetDefaultCredential(gatewayURL)
+
+	if defaultCreds == nil {
+		return nil, fmt.Errorf("no credentials found for gateway %s", gatewayURL)
+	}
+
+	if !defaultCreds.IsValid() {
+		return nil, fmt.Errorf("credentials for gateway %s are expired or invalid", gatewayURL)
+	}
+
+	return defaultCreds, nil
+}
diff --git a/pkg/gateway/auth_handlers.go b/pkg/gateway/auth_handlers.go
index a81e644..2060ae6 100644
--- a/pkg/gateway/auth_handlers.go
+++ b/pkg/gateway/auth_handlers.go
@@ -11,6 +11,7 @@ import (
 	"strings"
 	"time"
 
+	"git.debros.io/DeBros/network/pkg/client"
 	"git.debros.io/DeBros/network/pkg/storage"
 	ethcrypto "github.com/ethereum/go-ethereum/crypto"
 )
@@ -97,12 +98,14 @@ func (g *Gateway) challengeHandler(w http.ResponseWriter, r *http.Request) {
 
 	// Insert namespace if missing, fetch id
 	ctx := r.Context()
+	// Use internal context to bypass authentication for system operations
+	internalCtx := client.WithInternalAuth(ctx)
 	db := g.client.Database()
-	if _, err := db.Query(ctx, "INSERT OR IGNORE INTO namespaces(name) VALUES (?)", ns); err != nil {
+	if _, err := db.Query(internalCtx, "INSERT OR IGNORE INTO namespaces(name) VALUES (?)", ns); err != nil {
 		writeError(w, http.StatusInternalServerError, err.Error())
 		return
 	}
-	nres, err := db.Query(ctx, "SELECT id FROM namespaces WHERE name = ? LIMIT 1", ns)
+	nres, err := db.Query(internalCtx, "SELECT id FROM namespaces WHERE name = ? LIMIT 1", ns)
 	if err != nil || nres == nil || nres.Count == 0 || len(nres.Rows) == 0 || len(nres.Rows[0]) == 0 {
 		writeError(w, http.StatusInternalServerError, "failed to resolve namespace")
 		return
@@ -110,7 +113,7 @@ func (g *Gateway) challengeHandler(w http.ResponseWriter, r *http.Request) {
 	nsID := nres.Rows[0][0]
 
 	// Store nonce with 5 minute expiry
-	if _, err := db.Query(ctx,
+	if _, err := db.Query(internalCtx,
 		"INSERT INTO nonces(namespace_id, wallet, nonce, purpose, expires_at) VALUES (?, ?, ?, ?, datetime('now', '+5 minutes'))",
 		nsID, req.Wallet, nonce, req.Purpose,
 	); err != nil {
@@ -158,6 +161,8 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 	ctx := r.Context()
+	// Use internal context to bypass authentication for system operations
+	internalCtx := client.WithInternalAuth(ctx)
 	db := g.client.Database()
 	nsID, err := g.resolveNamespaceID(ctx, ns)
 	if err != nil {
@@ -165,7 +170,7 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	q := "SELECT id FROM nonces WHERE namespace_id = ? AND wallet = ? AND nonce = ? AND used_at IS NULL AND (expires_at IS NULL OR expires_at > datetime('now')) LIMIT 1"
-	nres, err := db.Query(ctx, q, nsID, req.Wallet, req.Nonce)
+	nres, err := db.Query(internalCtx, q, nsID, req.Wallet, req.Nonce)
 	if err != nil || nres == nil || nres.Count == 0 {
 		writeError(w, http.StatusBadRequest, "invalid or expired nonce")
 		return
@@ -206,7 +211,7 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
 	}
 
 	// Mark nonce used now (after successful verification)
-	if _, err := db.Query(ctx, "UPDATE nonces SET used_at = datetime('now') WHERE id = ?", nonceID); err != nil {
+	if _, err := db.Query(internalCtx, "UPDATE nonces SET used_at = datetime('now') WHERE id = ?", nonceID); err != nil {
 		writeError(w, http.StatusInternalServerError, err.Error())
 		return
 	}
@@ -227,7 +232,7 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	refresh := base64.RawURLEncoding.EncodeToString(rbuf)
-	if _, err := db.Query(ctx, "INSERT INTO refresh_tokens(namespace_id, subject, token, audience, expires_at) VALUES (?, ?, ?, ?, datetime('now', '+30 days'))", nsID, req.Wallet, refresh, "gateway"); err != nil {
+	if _, err := db.Query(internalCtx, "INSERT INTO refresh_tokens(namespace_id, subject, token, audience, expires_at) VALUES (?, ?, ?, ?, datetime('now', '+30 days'))", nsID, req.Wallet, refresh, "gateway"); err != nil {
 		writeError(w, http.StatusInternalServerError, err.Error())
 		return
 	}
@@ -282,6 +287,8 @@ func (g *Gateway) issueAPIKeyHandler(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 	ctx := r.Context()
+	// Use internal context to bypass authentication for system operations
+	internalCtx := client.WithInternalAuth(ctx)
 	db := g.client.Database()
 	// Resolve namespace id
 	nsID, err := g.resolveNamespaceID(ctx, ns)
@@ -291,7 +298,7 @@ func (g *Gateway) issueAPIKeyHandler(w http.ResponseWriter, r *http.Request) {
 	}
 	// Validate nonce exists and not used/expired
 	q := "SELECT id FROM nonces WHERE namespace_id = ? AND wallet = ? AND nonce = ? AND used_at IS NULL AND (expires_at IS NULL OR expires_at > datetime('now')) LIMIT 1"
-	nres, err := db.Query(ctx, q, nsID, req.Wallet, req.Nonce)
+	nres, err := db.Query(internalCtx, q, nsID, req.Wallet, req.Nonce)
 	if err != nil || nres == nil || nres.Count == 0 {
 		writeError(w, http.StatusBadRequest, "invalid or expired nonce")
 		return
@@ -326,13 +333,13 @@ func (g *Gateway) issueAPIKeyHandler(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	// Mark nonce used
-	if _, err := db.Query(ctx, "UPDATE nonces SET used_at = datetime('now') WHERE id = ?", nonceID); err != nil {
+	if _, err := db.Query(internalCtx, "UPDATE nonces SET used_at = datetime('now') WHERE id = ?", nonceID); err != nil {
 		writeError(w, http.StatusInternalServerError, err.Error())
 		return
 	}
 	// Check if api key exists for (namespace, wallet) via linkage table
 	var apiKey string
-	r1, err := db.Query(ctx, "SELECT api_keys.key FROM wallet_api_keys JOIN api_keys ON wallet_api_keys.api_key_id = api_keys.id WHERE wallet_api_keys.namespace_id = ? AND LOWER(wallet_api_keys.wallet) = LOWER(?) LIMIT 1", nsID, req.Wallet)
+	r1, err := db.Query(internalCtx, "SELECT api_keys.key FROM wallet_api_keys JOIN api_keys ON wallet_api_keys.api_key_id = api_keys.id WHERE wallet_api_keys.namespace_id = ? AND LOWER(wallet_api_keys.wallet) = LOWER(?) LIMIT 1", nsID, req.Wallet)
 	if err == nil && r1 != nil && r1.Count > 0 && len(r1.Rows) > 0 && len(r1.Rows[0]) > 0 {
 		if s, ok := r1.Rows[0][0].(string); ok {
 			apiKey = s
@@ -349,21 +356,21 @@ func (g *Gateway) issueAPIKeyHandler(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 		apiKey = "ak_" + base64.RawURLEncoding.EncodeToString(buf) + ":" + ns
-		if _, err := db.Query(ctx, "INSERT INTO api_keys(key, name, namespace_id) VALUES (?, ?, ?)", apiKey, "", nsID); err != nil {
+		if _, err := db.Query(internalCtx, "INSERT INTO api_keys(key, name, namespace_id) VALUES (?, ?, ?)", apiKey, "", nsID); err != nil {
 			writeError(w, http.StatusInternalServerError, err.Error())
 			return
 		}
 		// Create linkage
 		// Find api_key id
-		rid, err := db.Query(ctx, "SELECT id FROM api_keys WHERE key = ? LIMIT 1", apiKey)
+		rid, err := db.Query(internalCtx, "SELECT id FROM api_keys WHERE key = ? LIMIT 1", apiKey)
 		if err == nil && rid != nil && rid.Count > 0 && len(rid.Rows) > 0 && len(rid.Rows[0]) > 0 {
 			apiKeyID := rid.Rows[0][0]
-			_, _ = db.Query(ctx, "INSERT OR IGNORE INTO wallet_api_keys(namespace_id, wallet, api_key_id) VALUES (?, ?, ?)", nsID, strings.ToLower(req.Wallet), apiKeyID)
+			_, _ = db.Query(internalCtx, "INSERT OR IGNORE INTO wallet_api_keys(namespace_id, wallet, api_key_id) VALUES (?, ?, ?)", nsID, strings.ToLower(req.Wallet), apiKeyID)
 		}
 	}
 	// Record ownerships (best-effort)
-	_, _ = db.Query(ctx, "INSERT OR IGNORE INTO namespace_ownership(namespace_id, owner_type, owner_id) VALUES (?, 'api_key', ?)", nsID, apiKey)
-	_, _ = db.Query(ctx, "INSERT OR IGNORE INTO namespace_ownership(namespace_id, owner_type, owner_id) VALUES (?, 'wallet', ?)", nsID, req.Wallet)
+	_, _ = db.Query(internalCtx, "INSERT OR IGNORE INTO namespace_ownership(namespace_id, owner_type, owner_id) VALUES (?, 'api_key', ?)", nsID, apiKey)
+	_, _ = db.Query(internalCtx, "INSERT OR IGNORE INTO namespace_ownership(namespace_id, owner_type, owner_id) VALUES (?, 'wallet', ?)", nsID, req.Wallet)
 
 	writeJSON(w, http.StatusOK, map[string]any{
 		"api_key":   apiKey,
@@ -399,8 +406,10 @@ func (g *Gateway) apiKeyToJWTHandler(w http.ResponseWriter, r *http.Request) {
 	// Validate and get namespace
 	db := g.client.Database()
 	ctx := r.Context()
+	// Use internal context to bypass authentication for system operations
+	internalCtx := client.WithInternalAuth(ctx)
 	q := "SELECT namespaces.name FROM api_keys JOIN namespaces ON api_keys.namespace_id = namespaces.id WHERE api_keys.key = ? LIMIT 1"
-	res, err := db.Query(ctx, q, key)
+	res, err := db.Query(internalCtx, q, key)
 	if err != nil || res == nil || res.Count == 0 || len(res.Rows) == 0 || len(res.Rows[0]) == 0 {
 		writeError(w, http.StatusUnauthorized, "invalid API key")
 		return
@@ -467,6 +476,8 @@ func (g *Gateway) registerHandler(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 	ctx := r.Context()
+	// Use internal context to bypass authentication for system operations
+	internalCtx := client.WithInternalAuth(ctx)
 	db := g.client.Database()
 	nsID, err := g.resolveNamespaceID(ctx, ns)
 	if err != nil {
@@ -475,7 +486,7 @@ func (g *Gateway) registerHandler(w http.ResponseWriter, r *http.Request) {
 	}
 	// Validate nonce
 	q := "SELECT id FROM nonces WHERE namespace_id = ? AND wallet = ? AND nonce = ? AND used_at IS NULL AND (expires_at IS NULL OR expires_at > datetime('now')) LIMIT 1"
-	nres, err := db.Query(ctx, q, nsID, req.Wallet, req.Nonce)
+	nres, err := db.Query(internalCtx, q, nsID, req.Wallet, req.Nonce)
 	if err != nil || nres == nil || nres.Count == 0 || len(nres.Rows) == 0 || len(nres.Rows[0]) == 0 {
 		writeError(w, http.StatusBadRequest, "invalid or expired nonce")
 		return
@@ -515,7 +526,7 @@ func (g *Gateway) registerHandler(w http.ResponseWriter, r *http.Request) {
 	}
 
 	// Mark nonce used now (after successful verification)
-	if _, err := db.Query(ctx, "UPDATE nonces SET used_at = datetime('now') WHERE id = ?", nonceID); err != nil {
+	if _, err := db.Query(internalCtx, "UPDATE nonces SET used_at = datetime('now') WHERE id = ?", nonceID); err != nil {
 		writeError(w, http.StatusInternalServerError, err.Error())
 		return
 	}
@@ -533,13 +544,13 @@ func (g *Gateway) registerHandler(w http.ResponseWriter, r *http.Request) {
 	appID := "app_" + base64.RawURLEncoding.EncodeToString(buf)
 
 	// Persist app
-	if _, err := db.Query(ctx, "INSERT INTO apps(namespace_id, app_id, name, public_key) VALUES (?, ?, ?, ?)", nsID, appID, req.Name, pubHex); err != nil {
+	if _, err := db.Query(internalCtx, "INSERT INTO apps(namespace_id, app_id, name, public_key) VALUES (?, ?, ?, ?)", nsID, appID, req.Name, pubHex); err != nil {
 		writeError(w, http.StatusInternalServerError, err.Error())
 		return
 	}
 
 	// Record namespace ownership by wallet (best-effort)
-	_, _ = db.Query(ctx, "INSERT OR IGNORE INTO namespace_ownership(namespace_id, owner_type, owner_id) VALUES (?, ?, ?)", nsID, "wallet", req.Wallet)
+	_, _ = db.Query(internalCtx, "INSERT OR IGNORE INTO namespace_ownership(namespace_id, owner_type, owner_id) VALUES (?, ?, ?)", nsID, "wallet", req.Wallet)
 
 	writeJSON(w, http.StatusCreated, map[string]any{
 		"client_id": appID,
@@ -583,6 +594,8 @@ func (g *Gateway) refreshHandler(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 	ctx := r.Context()
+	// Use internal context to bypass authentication for system operations
+	internalCtx := client.WithInternalAuth(ctx)
 	db := g.client.Database()
 	nsID, err := g.resolveNamespaceID(ctx, ns)
 	if err != nil {
@@ -590,7 +603,7 @@ func (g *Gateway) refreshHandler(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	q := "SELECT subject FROM refresh_tokens WHERE namespace_id = ? AND token = ? AND revoked_at IS NULL AND (expires_at IS NULL OR expires_at > datetime('now')) LIMIT 1"
-	rres, err := db.Query(ctx, q, nsID, req.RefreshToken)
+	rres, err := db.Query(internalCtx, q, nsID, req.RefreshToken)
 	if err != nil || rres == nil || rres.Count == 0 {
 		writeError(w, http.StatusUnauthorized, "invalid or expired refresh token")
 		return
@@ -972,6 +985,8 @@ func (g *Gateway) logoutHandler(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 	ctx := r.Context()
+	// Use internal context to bypass authentication for system operations
+	internalCtx := client.WithInternalAuth(ctx)
 	db := g.client.Database()
 	nsID, err := g.resolveNamespaceID(ctx, ns)
 	if err != nil {
@@ -981,7 +996,7 @@ func (g *Gateway) logoutHandler(w http.ResponseWriter, r *http.Request) {
 
 	if strings.TrimSpace(req.RefreshToken) != "" {
 		// Revoke specific token
-		if _, err := db.Query(ctx, "UPDATE refresh_tokens SET revoked_at = datetime('now') WHERE namespace_id = ? AND token = ? AND revoked_at IS NULL", nsID, req.RefreshToken); err != nil {
+		if _, err := db.Query(internalCtx, "UPDATE refresh_tokens SET revoked_at = datetime('now') WHERE namespace_id = ? AND token = ? AND revoked_at IS NULL", nsID, req.RefreshToken); err != nil {
 			writeError(w, http.StatusInternalServerError, err.Error())
 			return
 		}
@@ -1001,7 +1016,7 @@ func (g *Gateway) logoutHandler(w http.ResponseWriter, r *http.Request) {
 			writeError(w, http.StatusUnauthorized, "jwt required for all=true")
 			return
 		}
-		if _, err := db.Query(ctx, "UPDATE refresh_tokens SET revoked_at = datetime('now') WHERE namespace_id = ? AND subject = ? AND revoked_at IS NULL", nsID, subject); err != nil {
+		if _, err := db.Query(internalCtx, "UPDATE refresh_tokens SET revoked_at = datetime('now') WHERE namespace_id = ? AND subject = ? AND revoked_at IS NULL", nsID, subject); err != nil {
 			writeError(w, http.StatusInternalServerError, err.Error())
 			return
 		}
diff --git a/pkg/gateway/db_helpers.go b/pkg/gateway/db_helpers.go
index 2c2852a..f6c8e0c 100644
--- a/pkg/gateway/db_helpers.go
+++ b/pkg/gateway/db_helpers.go
@@ -1,15 +1,19 @@
 package gateway
 
 import (
-    "context"
+	"context"
+
+	"git.debros.io/DeBros/network/pkg/client"
 )
 
 func (g *Gateway) resolveNamespaceID(ctx context.Context, ns string) (interface{}, error) {
+	// Use internal context to bypass authentication for system operations
+	internalCtx := client.WithInternalAuth(ctx)
 	db := g.client.Database()
-	if _, err := db.Query(ctx, "INSERT OR IGNORE INTO namespaces(name) VALUES (?)", ns); err != nil {
+	if _, err := db.Query(internalCtx, "INSERT OR IGNORE INTO namespaces(name) VALUES (?)", ns); err != nil {
 		return nil, err
 	}
-	res, err := db.Query(ctx, "SELECT id FROM namespaces WHERE name = ? LIMIT 1", ns)
+	res, err := db.Query(internalCtx, "SELECT id FROM namespaces WHERE name = ? LIMIT 1", ns)
 	if err != nil || res == nil || res.Count == 0 || len(res.Rows) == 0 || len(res.Rows[0]) == 0 {
 		return nil, err
 	}