termbox/internal/cli/root.go
Keiran b2c7dcae83 move auth commands to subcommands underneath auth
add tokens stored on the local device for persisting auth
2025-08-06 06:26:42 +01:00

230 lines
5.3 KiB
Go

package cli
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/spf13/cobra"
"golang.org/x/term"
)
const apiURL = "http://localhost:8080"
type Config struct {
Token string `json:"token"`
}
var rootCmd = &cobra.Command{
Use: "termbox",
Short: "Termbox CLI - A secure messaging platform",
Long: "Termbox CLI allows you to register, login, and send messages securely.",
}
var authCmd = &cobra.Command{
Use: "auth",
Short: "Authentication commands",
Long: "Commands for user authentication including register, login, and logout.",
}
var registerCmd = &cobra.Command{
Use: "register",
Short: "Register a new account",
Long: "Register a new account with username, email, and password. Includes email verification.",
Run: runRegister,
}
var loginCmd = &cobra.Command{
Use: "login",
Short: "Login to your account",
Long: "Login to your account with username and password.",
Run: runLogin,
}
var logoutCmd = &cobra.Command{
Use: "logout",
Short: "Logout from your account",
Long: "Clear stored authentication token.",
Run: runLogout,
}
func init() {
authCmd.AddCommand(registerCmd)
authCmd.AddCommand(loginCmd)
authCmd.AddCommand(logoutCmd)
rootCmd.AddCommand(authCmd)
}
func Execute() error {
return rootCmd.Execute()
}
func getConfigPath() string {
homeDir, _ := os.UserHomeDir()
return filepath.Join(homeDir, ".termbox", "config.json")
}
func loadConfig() (*Config, error) {
configPath := getConfigPath()
if _, err := os.Stat(configPath); os.IsNotExist(err) {
return &Config{}, nil
}
data, err := os.ReadFile(configPath)
if err != nil {
return nil, err
}
var config Config
err = json.Unmarshal(data, &config)
if err != nil {
return nil, err
}
return &config, nil
}
func saveConfig(config *Config) error {
configPath := getConfigPath()
configDir := filepath.Dir(configPath)
if err := os.MkdirAll(configDir, 0755); err != nil {
return err
}
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
return err
}
return os.WriteFile(configPath, data, 0600)
}
func runRegister(cmd *cobra.Command, args []string) {
fmt.Print("Username: ")
reader := bufio.NewReader(os.Stdin)
username, _ := reader.ReadString('\n')
username = strings.TrimSpace(username)
fmt.Print("Email: ")
email, _ := reader.ReadString('\n')
email = strings.TrimSpace(email)
fmt.Print("Password: ")
passwordBytes, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
fmt.Printf("Error reading password: %v\n", err)
return
}
password := string(passwordBytes)
fmt.Println()
requestBody, _ := json.Marshal(map[string]string{
"username": username,
"email": email,
"password": password,
})
resp, err := http.Post(apiURL+"/auth/register", "application/json", bytes.NewBuffer(requestBody))
if err != nil {
fmt.Printf("Error registering: %v\n", err)
return
}
defer resp.Body.Close()
var response map[string]interface{}
json.NewDecoder(resp.Body).Decode(&response)
if resp.StatusCode == http.StatusCreated {
fmt.Printf("Registration successful! Check your email (%s) for verification code.\n", email)
fmt.Print("Verification code: ")
code, _ := reader.ReadString('\n')
code = strings.TrimSpace(code)
verifyBody, _ := json.Marshal(map[string]string{
"email": email,
"code": code,
})
verifyResp, err := http.Post(apiURL+"/auth/verify", "application/json", bytes.NewBuffer(verifyBody))
if err != nil {
fmt.Printf("Error verifying: %v\n", err)
return
}
defer verifyResp.Body.Close()
var verifyResponse map[string]interface{}
json.NewDecoder(verifyResp.Body).Decode(&verifyResponse)
if verifyResp.StatusCode == http.StatusOK {
fmt.Println("Account verified successfully! You can now login.")
} else {
fmt.Printf("Verification failed: %s\n", verifyResponse["error"])
}
} else {
fmt.Printf("Registration failed: %s\n", response["error"])
}
}
func runLogin(cmd *cobra.Command, args []string) {
fmt.Print("Username: ")
reader := bufio.NewReader(os.Stdin)
username, _ := reader.ReadString('\n')
username = strings.TrimSpace(username)
fmt.Print("Password: ")
passwordBytes, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
fmt.Printf("Error reading password: %v\n", err)
return
}
password := string(passwordBytes)
fmt.Println()
requestBody, _ := json.Marshal(map[string]string{
"username": username,
"password": password,
})
resp, err := http.Post(apiURL+"/auth/login", "application/json", bytes.NewBuffer(requestBody))
if err != nil {
fmt.Printf("Error logging in: %v\n", err)
return
}
defer resp.Body.Close()
var response map[string]interface{}
json.NewDecoder(resp.Body).Decode(&response)
if resp.StatusCode == http.StatusOK {
user := response["user"].(map[string]interface{})
token := response["token"].(string)
config := &Config{Token: token}
if err := saveConfig(config); err != nil {
fmt.Printf("Warning: Failed to save login token: %v\n", err)
}
fmt.Printf("Login successful! Welcome back, %s!\n", user["username"])
} else {
fmt.Printf("Login failed: %s\n", response["error"])
}
}
func runLogout(cmd *cobra.Command, args []string) {
config := &Config{}
if err := saveConfig(config); err != nil {
fmt.Printf("Error clearing token: %v\n", err)
return
}
fmt.Println("Logged out successfully.")
}