/* Copyright © 2025 maxtheweb */ package cmd import ( "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "time" "github.com/BurntSushi/toml" "github.com/spf13/cobra" ) // Config structure type Config struct { OAuth struct { ConsumerKey string `toml:"consumer_key"` ConsumerSecret string `toml:"consumer_secret"` AccessToken string `toml:"access_token"` AccessTokenSecret string `toml:"access_token_secret"` } `toml:"oauth"` OAuth2 struct { ClientID string `toml:"client_id"` ClientSecret string `toml:"client_secret"` } `toml:"oauth2"` } // UserMeResponse from X API /2/users/me type UserMeResponse struct { Data struct { ID string `json:"id"` Name string `json:"name"` Username string `json:"username"` } `json:"data"` Errors []struct { Message string `json:"message"` } `json:"errors"` } // statusCmd represents the status command var statusCmd = &cobra.Command{ Use: "status", Short: "Test OAuth authentication", Long: `Test your OAuth 1.0a credentials by authenticating with X API. This command verifies that your credentials are valid by making an authenticated request to /2/users/me to fetch your user information. Run this after 'xapi login' to confirm your setup is correct.`, Run: func(cmd *cobra.Command, args []string) { runStatus() }, } func init() { rootCmd.AddCommand(statusCmd) } func runStatus() { fmt.Println("\nTESTING AUTHENTICATION") fmt.Println("======================") // Load OAuth config config, err := loadConfig() if err != nil { fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err) fmt.Println("\nRun 'xapi login' to configure your credentials") os.Exit(1) } // Check OAuth credentials if config.OAuth.ConsumerKey == "" { fmt.Fprintf(os.Stderr, "OAuth credentials not configured\n") fmt.Println("Run 'xapi login' to configure your credentials") os.Exit(1) } fmt.Println("\nAuthenticating with X API...") // Make authenticated request to /2/users/me apiURL := "https://api.x.com/2/users/me" // Create HTTP client client := &http.Client{ Timeout: 30 * time.Second, } req, err := http.NewRequest("GET", apiURL, nil) if err != nil { fmt.Fprintf(os.Stderr, "Error creating request: %v\n", err) os.Exit(1) } // Generate OAuth 1.0a Authorization header oauthClient := NewOAuthClient(config) authHeader := oauthClient.GetAuthorizationHeader("GET", apiURL, map[string]string{}) req.Header.Set("Authorization", authHeader) resp, err := client.Do(req) if err != nil { fmt.Fprintf(os.Stderr, "Error making API request: %v\n", err) os.Exit(1) } defer resp.Body.Close() // Read response body body, err := io.ReadAll(resp.Body) if err != nil { fmt.Fprintf(os.Stderr, "Error reading response: %v\n", err) os.Exit(1) } // Check status if resp.StatusCode != http.StatusOK { fmt.Fprintf(os.Stderr, "\nAUTHENTICATION FAILED\n") fmt.Fprintf(os.Stderr, "Status: %d\n", resp.StatusCode) fmt.Fprintf(os.Stderr, "Response: %s\n", string(body)) fmt.Println("\nPlease check your OAuth credentials:") fmt.Println(" - Consumer Key (API Key)") fmt.Println(" - Consumer Secret (API Secret)") fmt.Println(" - Access Token") fmt.Println(" - Access Token Secret") fmt.Println("\nRun 'xapi login' to reconfigure") os.Exit(1) } // Parse response var userResp UserMeResponse if err := json.Unmarshal(body, &userResp); err != nil { fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err) os.Exit(1) } // Check for API errors if len(userResp.Errors) > 0 { fmt.Println("\nAUTHENTICATION FAILED") for _, err := range userResp.Errors { fmt.Printf(" - %s\n", err.Message) } os.Exit(1) } // Success! fmt.Println("\nAUTHENTICATION SUCCESSFUL") fmt.Println("=========================") fmt.Printf("\nAuthenticated as:\n") fmt.Printf(" Name: %s\n", userResp.Data.Name) fmt.Printf(" Username: @%s\n", userResp.Data.Username) fmt.Printf(" User ID: %s\n", userResp.Data.ID) fmt.Println("\nYour OAuth credentials are working correctly!") fmt.Println("You can now use 'xapi search' and 'xapi create'") fmt.Println() } func loadConfig() (*Config, error) { homeDir, err := os.UserHomeDir() if err != nil { return nil, err } configFile := filepath.Join(homeDir, ".config", "xapi", "config.toml") var config Config if _, err := toml.DecodeFile(configFile, &config); err != nil { return nil, err } return &config, nil }