mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 14:36:58 +00:00
467 lines
14 KiB
Go
467 lines
14 KiB
Go
package cli
|
||
|
||
import (
|
||
"bufio"
|
||
"flag"
|
||
"fmt"
|
||
"os"
|
||
"strings"
|
||
|
||
"github.com/DeBrosOfficial/network/pkg/auth"
|
||
)
|
||
|
||
// HandleAuthCommand handles authentication commands
|
||
func HandleAuthCommand(args []string) {
|
||
if len(args) == 0 {
|
||
showAuthHelp()
|
||
return
|
||
}
|
||
|
||
subcommand := args[0]
|
||
switch subcommand {
|
||
case "login":
|
||
var wallet, namespace string
|
||
var simple bool
|
||
fs := flag.NewFlagSet("auth login", flag.ExitOnError)
|
||
fs.StringVar(&wallet, "wallet", "", "Wallet address (implies --simple)")
|
||
fs.StringVar(&namespace, "namespace", "", "Namespace name")
|
||
fs.BoolVar(&simple, "simple", false, "Use simple auth without signature verification")
|
||
_ = fs.Parse(args[1:])
|
||
handleAuthLogin(wallet, namespace, simple)
|
||
case "logout":
|
||
handleAuthLogout()
|
||
case "whoami":
|
||
handleAuthWhoami()
|
||
case "status":
|
||
handleAuthStatus()
|
||
case "list":
|
||
handleAuthList()
|
||
case "switch":
|
||
handleAuthSwitch()
|
||
default:
|
||
fmt.Fprintf(os.Stderr, "Unknown auth command: %s\n", subcommand)
|
||
showAuthHelp()
|
||
os.Exit(1)
|
||
}
|
||
}
|
||
|
||
func showAuthHelp() {
|
||
fmt.Printf("🔐 Authentication Commands\n\n")
|
||
fmt.Printf("Usage: orama auth <subcommand>\n\n")
|
||
fmt.Printf("Subcommands:\n")
|
||
fmt.Printf(" login - Authenticate with RootWallet (default) or simple auth\n")
|
||
fmt.Printf(" logout - Clear stored credentials\n")
|
||
fmt.Printf(" whoami - Show current authentication status\n")
|
||
fmt.Printf(" status - Show detailed authentication info\n")
|
||
fmt.Printf(" list - List all stored credentials for current environment\n")
|
||
fmt.Printf(" switch - Switch between stored credentials\n\n")
|
||
fmt.Printf("Login Flags:\n")
|
||
fmt.Printf(" --namespace <name> - Target namespace\n")
|
||
fmt.Printf(" --simple - Use simple auth (no signature, dev only)\n")
|
||
fmt.Printf(" --wallet <0x...> - Wallet address (implies --simple)\n\n")
|
||
fmt.Printf("Examples:\n")
|
||
fmt.Printf(" orama auth login # Sign with RootWallet (default)\n")
|
||
fmt.Printf(" orama auth login --namespace myns # Sign with RootWallet + namespace\n")
|
||
fmt.Printf(" orama auth login --simple # Simple auth (no signature)\n")
|
||
fmt.Printf(" orama auth whoami # Check who you're logged in as\n")
|
||
fmt.Printf(" orama auth logout # Clear all stored credentials\n\n")
|
||
fmt.Printf("Environment Variables:\n")
|
||
fmt.Printf(" ORAMA_GATEWAY_URL - Gateway URL (overrides environment config)\n\n")
|
||
fmt.Printf("Authentication Flow (RootWallet):\n")
|
||
fmt.Printf(" 1. Run 'orama auth login'\n")
|
||
fmt.Printf(" 2. Your wallet address is read from RootWallet automatically\n")
|
||
fmt.Printf(" 3. Enter your namespace when prompted\n")
|
||
fmt.Printf(" 4. A challenge nonce is signed with your wallet key\n")
|
||
fmt.Printf(" 5. Credentials are saved to ~/.orama/credentials.json\n\n")
|
||
fmt.Printf("Note: Requires RootWallet CLI (rw) in PATH.\n")
|
||
fmt.Printf(" Install: cd rootwallet/cli && ./install.sh\n")
|
||
fmt.Printf(" Authentication uses the currently active environment.\n")
|
||
fmt.Printf(" Use 'orama env current' to see your active environment.\n")
|
||
}
|
||
|
||
func handleAuthLogin(wallet, namespace string, simple bool) {
|
||
// Get gateway URL from active environment
|
||
gatewayURL := getGatewayURL()
|
||
|
||
// Show active environment
|
||
env, err := GetActiveEnvironment()
|
||
if err == nil {
|
||
fmt.Printf("🌍 Environment: %s\n", env.Name)
|
||
}
|
||
fmt.Printf("🔐 Authenticating with gateway at: %s\n\n", gatewayURL)
|
||
|
||
// Load enhanced credential store
|
||
store, err := auth.LoadEnhancedCredentials()
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "❌ Failed to load credentials: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
// Check if we already have credentials for this gateway
|
||
gwCreds := store.Gateways[gatewayURL]
|
||
if gwCreds != nil && len(gwCreds.Credentials) > 0 {
|
||
// Show existing credentials and offer choice
|
||
choice, credIndex, err := store.DisplayCredentialMenu(gatewayURL)
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "❌ Menu selection failed: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
switch choice {
|
||
case auth.AuthChoiceUseCredential:
|
||
selectedCreds := gwCreds.Credentials[credIndex]
|
||
store.SetDefaultCredential(gatewayURL, credIndex)
|
||
selectedCreds.UpdateLastUsed()
|
||
if err := store.Save(); err != nil {
|
||
fmt.Fprintf(os.Stderr, "❌ Failed to save credentials: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
fmt.Printf("✅ Switched to wallet: %s\n", selectedCreds.Wallet)
|
||
fmt.Printf("🏢 Namespace: %s\n", selectedCreds.Namespace)
|
||
return
|
||
|
||
case auth.AuthChoiceLogout:
|
||
store.ClearAllCredentials()
|
||
if err := store.Save(); err != nil {
|
||
fmt.Fprintf(os.Stderr, "❌ Failed to clear credentials: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
fmt.Println("✅ All credentials cleared")
|
||
return
|
||
|
||
case auth.AuthChoiceExit:
|
||
fmt.Println("Exiting...")
|
||
return
|
||
|
||
case auth.AuthChoiceAddCredential:
|
||
// Fall through to add new credential
|
||
}
|
||
}
|
||
|
||
// Choose authentication method
|
||
var creds *auth.Credentials
|
||
reader := bufio.NewReader(os.Stdin)
|
||
|
||
if simple || wallet != "" {
|
||
// Explicit simple auth — requires existing credentials
|
||
existingCreds := store.GetDefaultCredential(gatewayURL)
|
||
if existingCreds == nil || !existingCreds.IsValid() {
|
||
fmt.Fprintf(os.Stderr, "❌ Simple auth requires existing credentials. Authenticate with RootWallet or Phantom first.\n")
|
||
os.Exit(1)
|
||
}
|
||
creds, err = auth.PerformSimpleAuthentication(gatewayURL, wallet, namespace, existingCreds.APIKey)
|
||
} else {
|
||
// Show auth method selection
|
||
fmt.Println("How would you like to authenticate?")
|
||
fmt.Println(" 1. RootWallet (EVM signature)")
|
||
fmt.Println(" 2. Phantom (Solana + NFT required)")
|
||
fmt.Print("\nSelect [1/2]: ")
|
||
|
||
choice, _ := reader.ReadString('\n')
|
||
choice = strings.TrimSpace(choice)
|
||
|
||
switch choice {
|
||
case "2":
|
||
creds, err = auth.PerformPhantomAuthentication(gatewayURL, namespace)
|
||
default:
|
||
// Default to RootWallet
|
||
if auth.IsRootWalletInstalled() {
|
||
creds, err = auth.PerformRootWalletAuthentication(gatewayURL, namespace)
|
||
} else {
|
||
fmt.Println("\n⚠️ RootWallet CLI (rw) not found in PATH.")
|
||
fmt.Println(" Install it: cd rootwallet/cli && ./install.sh")
|
||
os.Exit(1)
|
||
}
|
||
}
|
||
}
|
||
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "❌ Authentication failed: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
// Add to enhanced store
|
||
store.AddCredential(gatewayURL, creds)
|
||
|
||
// Set as default
|
||
gwCreds = store.Gateways[gatewayURL]
|
||
if gwCreds != nil {
|
||
store.SetDefaultCredential(gatewayURL, len(gwCreds.Credentials)-1)
|
||
}
|
||
|
||
if err := store.Save(); 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)
|
||
if creds.NamespaceURL != "" {
|
||
fmt.Printf("🌐 Namespace URL: %s\n", creds.NamespaceURL)
|
||
}
|
||
}
|
||
|
||
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.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.Println("❌ Not authenticated - run 'orama auth login' to authenticate")
|
||
os.Exit(1)
|
||
}
|
||
|
||
fmt.Println("✅ Authenticated")
|
||
fmt.Printf(" Wallet: %s\n", creds.Wallet)
|
||
fmt.Printf(" Namespace: %s\n", creds.Namespace)
|
||
if creds.NamespaceURL != "" {
|
||
fmt.Printf(" NS Gateway: %s\n", creds.NamespaceURL)
|
||
}
|
||
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.LoadEnhancedCredentials()
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "❌ Failed to load credentials: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
gatewayURL := getGatewayURL()
|
||
creds := store.GetDefaultCredential(gatewayURL)
|
||
|
||
// Show active environment
|
||
env, err := GetActiveEnvironment()
|
||
if err == nil {
|
||
fmt.Printf("🌍 Active Environment: %s\n", env.Name)
|
||
}
|
||
|
||
fmt.Println("🔐 Authentication Status")
|
||
fmt.Printf(" Gateway URL: %s\n", gatewayURL)
|
||
|
||
if 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.NamespaceURL != "" {
|
||
fmt.Printf(" NS Gateway: %s\n", creds.NamespaceURL)
|
||
}
|
||
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"))
|
||
}
|
||
}
|
||
|
||
// promptForGatewayURL interactively prompts for the gateway URL
|
||
// Uses the active environment or allows entering a custom domain
|
||
func promptForGatewayURL() string {
|
||
// Check environment variable first (allows override without prompting)
|
||
if url := os.Getenv("ORAMA_GATEWAY_URL"); url != "" {
|
||
return url
|
||
}
|
||
|
||
// Try active environment
|
||
env, err := GetActiveEnvironment()
|
||
if err == nil {
|
||
reader := bufio.NewReader(os.Stdin)
|
||
|
||
fmt.Println("\n🌐 Node Connection")
|
||
fmt.Println("==================")
|
||
fmt.Printf("1. Use active environment: %s (%s)\n", env.Name, env.GatewayURL)
|
||
fmt.Println("2. Enter custom domain")
|
||
fmt.Print("\nSelect option [1/2]: ")
|
||
|
||
choice, _ := reader.ReadString('\n')
|
||
choice = strings.TrimSpace(choice)
|
||
|
||
if choice == "1" || choice == "" {
|
||
return env.GatewayURL
|
||
}
|
||
|
||
if choice == "2" {
|
||
fmt.Print("Enter node domain (e.g., node-hk19de.orama.network): ")
|
||
domain, _ := reader.ReadString('\n')
|
||
domain = strings.TrimSpace(domain)
|
||
|
||
if domain == "" {
|
||
fmt.Printf("⚠️ No domain entered, using %s\n", env.Name)
|
||
return env.GatewayURL
|
||
}
|
||
|
||
// Remove any protocol prefix if user included it
|
||
domain = strings.TrimPrefix(domain, "https://")
|
||
domain = strings.TrimPrefix(domain, "http://")
|
||
// Remove trailing slash
|
||
domain = strings.TrimSuffix(domain, "/")
|
||
|
||
return fmt.Sprintf("https://%s", domain)
|
||
}
|
||
|
||
return env.GatewayURL
|
||
}
|
||
|
||
return "https://orama-devnet.network"
|
||
}
|
||
|
||
// getGatewayURL returns the gateway URL based on environment or env var
|
||
// Used by other commands that don't need interactive node selection
|
||
func getGatewayURL() string {
|
||
// Check environment variable first (for backwards compatibility)
|
||
if url := os.Getenv("ORAMA_GATEWAY_URL"); url != "" {
|
||
return url
|
||
}
|
||
|
||
// Get from active environment
|
||
env, err := GetActiveEnvironment()
|
||
if err == nil {
|
||
return env.GatewayURL
|
||
}
|
||
|
||
// Fallback to devnet
|
||
return "https://orama-devnet.network"
|
||
}
|
||
|
||
func handleAuthList() {
|
||
store, err := auth.LoadEnhancedCredentials()
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "❌ Failed to load credentials: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
gatewayURL := getGatewayURL()
|
||
|
||
// Show active environment
|
||
env, err := GetActiveEnvironment()
|
||
if err == nil {
|
||
fmt.Printf("🌍 Environment: %s\n", env.Name)
|
||
}
|
||
fmt.Printf("🔗 Gateway: %s\n\n", gatewayURL)
|
||
|
||
gwCreds := store.Gateways[gatewayURL]
|
||
if gwCreds == nil || len(gwCreds.Credentials) == 0 {
|
||
fmt.Println("No credentials stored for this environment.")
|
||
fmt.Println("Run 'orama auth login' to authenticate.")
|
||
return
|
||
}
|
||
|
||
fmt.Printf("🔐 Stored Credentials (%d):\n\n", len(gwCreds.Credentials))
|
||
for i, creds := range gwCreds.Credentials {
|
||
defaultMark := ""
|
||
if i == gwCreds.DefaultIndex {
|
||
defaultMark = " ← active"
|
||
}
|
||
|
||
statusEmoji := "✅"
|
||
statusText := "valid"
|
||
if !creds.IsValid() {
|
||
statusEmoji = "❌"
|
||
statusText = "expired"
|
||
}
|
||
|
||
fmt.Printf(" %d. %s Wallet: %s%s\n", i+1, statusEmoji, creds.Wallet, defaultMark)
|
||
fmt.Printf(" Namespace: %s | Status: %s\n", creds.Namespace, statusText)
|
||
if creds.Plan != "" {
|
||
fmt.Printf(" Plan: %s\n", creds.Plan)
|
||
}
|
||
if !creds.IssuedAt.IsZero() {
|
||
fmt.Printf(" Issued: %s\n", creds.IssuedAt.Format("2006-01-02 15:04:05"))
|
||
}
|
||
fmt.Println()
|
||
}
|
||
}
|
||
|
||
func handleAuthSwitch() {
|
||
store, err := auth.LoadEnhancedCredentials()
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "❌ Failed to load credentials: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
gatewayURL := getGatewayURL()
|
||
|
||
gwCreds := store.Gateways[gatewayURL]
|
||
if gwCreds == nil || len(gwCreds.Credentials) == 0 {
|
||
fmt.Println("No credentials stored for this environment.")
|
||
fmt.Println("Run 'orama auth login' to authenticate first.")
|
||
os.Exit(1)
|
||
}
|
||
|
||
if len(gwCreds.Credentials) == 1 {
|
||
fmt.Println("Only one credential stored. Nothing to switch to.")
|
||
return
|
||
}
|
||
|
||
// Display menu
|
||
choice, credIndex, err := store.DisplayCredentialMenu(gatewayURL)
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "❌ Menu selection failed: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
|
||
switch choice {
|
||
case auth.AuthChoiceUseCredential:
|
||
selectedCreds := gwCreds.Credentials[credIndex]
|
||
store.SetDefaultCredential(gatewayURL, credIndex)
|
||
selectedCreds.UpdateLastUsed()
|
||
if err := store.Save(); err != nil {
|
||
fmt.Fprintf(os.Stderr, "❌ Failed to save credentials: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
fmt.Printf("✅ Switched to wallet: %s\n", selectedCreds.Wallet)
|
||
fmt.Printf("🏢 Namespace: %s\n", selectedCreds.Namespace)
|
||
|
||
case auth.AuthChoiceAddCredential:
|
||
fmt.Println("Use 'orama auth login' to add a new credential.")
|
||
|
||
case auth.AuthChoiceLogout:
|
||
store.ClearAllCredentials()
|
||
if err := store.Save(); err != nil {
|
||
fmt.Fprintf(os.Stderr, "❌ Failed to clear credentials: %v\n", err)
|
||
os.Exit(1)
|
||
}
|
||
fmt.Println("✅ All credentials cleared")
|
||
|
||
case auth.AuthChoiceExit:
|
||
fmt.Println("Cancelled.")
|
||
}
|
||
}
|