230 lines
5.3 KiB
Go
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.")
|
|
}
|