From 60affaec5c2220f1fe9e03ce50e5c260eaa58e85 Mon Sep 17 00:00:00 2001 From: anonpenguin23 Date: Fri, 24 Oct 2025 10:01:53 +0300 Subject: [PATCH] Enhance configuration validation for nodes and gateways, implementing strict YAML decoding to reject unknown fields. Added comprehensive validation checks for node types, listen addresses, database settings, and discovery parameters. Updated README with configuration validation details and examples. --- CHANGELOG.md | 7 ++ Makefile | 2 +- cmd/cli/main.go | 145 +++++++++++++++++++++++++++++++++ pkg/gateway/config_validate.go | 38 +++++++-- 4 files changed, 182 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29758a2..33d119d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,21 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant ### Added +- Added validation for yaml files +- Added authenticaiton command on cli + ### Changed +- Updated readme + ### Deprecated ### Removed ### Fixed +- Regular nodes rqlite not starting + ## [0.51.2] - 2025-09-26 ### Added diff --git a/Makefile b/Makefile index a77870b..4e611b4 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ test-e2e: .PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet lint clear-ports -VERSION := 0.51.3-beta +VERSION := 0.51.4-beta COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)' diff --git a/cmd/cli/main.go b/cmd/cli/main.go index e115113..f9acc47 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -76,6 +76,8 @@ func main() { handleConnect(args[0]) case "peer-id": handlePeerID() + case "auth": + handleAuth(args) case "help", "--help", "-h": showHelp() @@ -289,6 +291,145 @@ func handlePubSub(args []string) { } } +func handleAuth(args []string) { + if len(args) == 0 { + showAuthHelp() + return + } + + subcommand := args[0] + switch subcommand { + case "login": + handleAuthLogin() + case "logout": + handleAuthLogout() + case "whoami": + handleAuthWhoami() + case "status": + handleAuthStatus() + default: + fmt.Fprintf(os.Stderr, "Unknown auth command: %s\n", subcommand) + showAuthHelp() + os.Exit(1) + } +} + +func handleAuthLogin() { + gatewayURL := auth.GetDefaultGatewayURL() + fmt.Printf("🔐 Authenticating with gateway at: %s\n", gatewayURL) + + // Use the wallet authentication flow + creds, err := auth.PerformWalletAuthentication(gatewayURL) + if err != nil { + fmt.Fprintf(os.Stderr, "❌ Authentication failed: %v\n", err) + os.Exit(1) + } + + // Save credentials to file + if err := auth.SaveCredentialsForDefaultGateway(creds); err != nil { + fmt.Fprintf(os.Stderr, "❌ Failed to save credentials: %v\n", err) + os.Exit(1) + } + + credsPath, _ := auth.GetCredentialsPath() + fmt.Printf("✅ Authentication successful!\n") + fmt.Printf("📁 Credentials saved to: %s\n", credsPath) + fmt.Printf("🎯 Wallet: %s\n", creds.Wallet) + fmt.Printf("🏢 Namespace: %s\n", creds.Namespace) +} + +func handleAuthLogout() { + if err := auth.ClearAllCredentials(); err != nil { + fmt.Fprintf(os.Stderr, "❌ Failed to clear credentials: %v\n", err) + os.Exit(1) + } + fmt.Println("✅ Logged out successfully - all credentials have been cleared") +} + +func handleAuthWhoami() { + store, err := auth.LoadCredentials() + if err != nil { + fmt.Fprintf(os.Stderr, "❌ Failed to load credentials: %v\n", err) + os.Exit(1) + } + + gatewayURL := auth.GetDefaultGatewayURL() + creds, exists := store.GetCredentialsForGateway(gatewayURL) + + if !exists || !creds.IsValid() { + fmt.Println("❌ Not authenticated - run 'network-cli auth login' to authenticate") + os.Exit(1) + } + + fmt.Println("✅ Authenticated") + fmt.Printf(" Wallet: %s\n", creds.Wallet) + fmt.Printf(" Namespace: %s\n", creds.Namespace) + fmt.Printf(" Issued At: %s\n", creds.IssuedAt.Format("2006-01-02 15:04:05")) + if !creds.ExpiresAt.IsZero() { + fmt.Printf(" Expires At: %s\n", creds.ExpiresAt.Format("2006-01-02 15:04:05")) + } + if !creds.LastUsedAt.IsZero() { + fmt.Printf(" Last Used: %s\n", creds.LastUsedAt.Format("2006-01-02 15:04:05")) + } + if creds.Plan != "" { + fmt.Printf(" Plan: %s\n", creds.Plan) + } +} + +func handleAuthStatus() { + store, err := auth.LoadCredentials() + if err != nil { + fmt.Fprintf(os.Stderr, "❌ Failed to load credentials: %v\n", err) + os.Exit(1) + } + + gatewayURL := auth.GetDefaultGatewayURL() + creds, exists := store.GetCredentialsForGateway(gatewayURL) + + fmt.Println("🔐 Authentication Status") + fmt.Printf(" Gateway URL: %s\n", gatewayURL) + + if !exists || creds == nil { + fmt.Println(" Status: ❌ Not authenticated") + return + } + + if !creds.IsValid() { + fmt.Println(" Status: ⚠️ Credentials expired") + if !creds.ExpiresAt.IsZero() { + fmt.Printf(" Expired At: %s\n", creds.ExpiresAt.Format("2006-01-02 15:04:05")) + } + return + } + + fmt.Println(" Status: ✅ Authenticated") + fmt.Printf(" Wallet: %s\n", creds.Wallet) + fmt.Printf(" Namespace: %s\n", creds.Namespace) + if !creds.ExpiresAt.IsZero() { + fmt.Printf(" Expires: %s\n", creds.ExpiresAt.Format("2006-01-02 15:04:05")) + } + if !creds.LastUsedAt.IsZero() { + fmt.Printf(" Last Used: %s\n", creds.LastUsedAt.Format("2006-01-02 15:04:05")) + } +} + +func showAuthHelp() { + fmt.Printf("🔐 Authentication Commands\n\n") + fmt.Printf("Usage: network-cli auth \n\n") + fmt.Printf("Subcommands:\n") + fmt.Printf(" login - Authenticate with wallet\n") + fmt.Printf(" logout - Clear stored credentials\n") + fmt.Printf(" whoami - Show current authentication status\n") + fmt.Printf(" status - Show detailed authentication info\n\n") + fmt.Printf("Examples:\n") + fmt.Printf(" network-cli auth login\n") + fmt.Printf(" network-cli auth whoami\n") + fmt.Printf(" network-cli auth status\n") + fmt.Printf(" network-cli auth logout\n\n") + fmt.Printf("Environment Variables:\n") + fmt.Printf(" DEBROS_GATEWAY_URL - Gateway URL (default: http://localhost:6001)\n") +} + func ensureAuthenticated() *auth.Credentials { gatewayURL := auth.GetDefaultGatewayURL() @@ -440,6 +581,7 @@ func showHelp() { 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(" auth 🔐 Authentication management (login, logout, whoami, status)\n") fmt.Printf(" health - Check network health\n") fmt.Printf(" peers - List connected peers\n") fmt.Printf(" status - Show network status\n") @@ -457,10 +599,13 @@ func showHelp() { 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(" Use 'network-cli auth login' to authenticate with your wallet\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 auth login\n") + fmt.Printf(" network-cli auth whoami\n") fmt.Printf(" network-cli health\n") fmt.Printf(" network-cli peer-id\n") fmt.Printf(" network-cli peer-id --format json\n") diff --git a/pkg/gateway/config_validate.go b/pkg/gateway/config_validate.go index ebb4686..a185107 100644 --- a/pkg/gateway/config_validate.go +++ b/pkg/gateway/config_validate.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/multiformats/go-multiaddr" - manet "github.com/multiformats/go-multiaddr/net" ) // ValidateConfig performs comprehensive validation of gateway configuration. @@ -35,7 +34,7 @@ func (c *Config) ValidateConfig() []error { for i, peer := range c.BootstrapPeers { path := fmt.Sprintf("gateway.bootstrap_peers[%d]", i) - ma, err := multiaddr.NewMultiaddr(peer) + _, err := multiaddr.NewMultiaddr(peer) if err != nil { errs = append(errs, fmt.Errorf("%s: invalid multiaddr: %v; expected /ip{4,6}/.../tcp//p2p/", path, err)) continue @@ -46,16 +45,16 @@ func (c *Config) ValidateConfig() []error { errs = append(errs, fmt.Errorf("%s: missing /p2p/ component; expected /ip{4,6}/.../tcp//p2p/", path)) } - // Try to extract TCP addr to validate port - tcpAddr, err := manet.ToNetAddr(ma) - if err != nil { - errs = append(errs, fmt.Errorf("%s: cannot convert to network address: %v", path, err)) + // Extract TCP port by parsing the multiaddr string directly + tcpPortStr := extractTCPPort(peer) + if tcpPortStr == "" { + errs = append(errs, fmt.Errorf("%s: missing /tcp/ component; expected /ip{4,6}/.../tcp//p2p/", path)) continue } - tcpPort := tcpAddr.(*net.TCPAddr).Port - if tcpPort < 1 || tcpPort > 65535 { - errs = append(errs, fmt.Errorf("%s: invalid TCP port %d; port must be between 1 and 65535", path, tcpPort)) + tcpPort, err := strconv.Atoi(tcpPortStr) + if err != nil || tcpPort < 1 || tcpPort > 65535 { + errs = append(errs, fmt.Errorf("%s: invalid TCP port %s; port must be between 1 and 65535", path, tcpPortStr)) } if seenPeers[peer] { @@ -115,3 +114,24 @@ func validateRQLiteDSN(dsn string) error { return nil } + +// extractTCPPort extracts the TCP port from a multiaddr string. +// It assumes the multiaddr is in the format /ip{4,6}/.../tcp//p2p/. +func extractTCPPort(multiaddrStr string) string { + // Find the last /tcp/ component + lastTCPIndex := strings.LastIndex(multiaddrStr, "/tcp/") + if lastTCPIndex == -1 { + return "" + } + + // Extract the port part after /tcp/ + portPart := multiaddrStr[lastTCPIndex+len("/tcp/"):] + + // Find the first / component after the port part + firstSlashIndex := strings.Index(portPart, "/") + if firstSlashIndex == -1 { + return portPart + } + + return portPart[:firstSlashIndex] +}