mirror of
https://github.com/hanzoskill/sogcli.git
synced 2026-10-30 21:03:29 +00:00
330 lines
9.0 KiB
Go
330 lines
9.0 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/visionik/sogcli/internal/config"
|
|
"github.com/visionik/sogcli/internal/discover"
|
|
"github.com/visionik/sogcli/internal/imap"
|
|
"github.com/visionik/sogcli/internal/smtp"
|
|
)
|
|
|
|
// AuthCmd handles account management.
|
|
type AuthCmd struct {
|
|
Add AuthAddCmd `cmd:"" help:"Add an IMAP/SMTP account"`
|
|
List AuthListCmd `cmd:"" help:"List configured accounts"`
|
|
Test AuthTestCmd `cmd:"" help:"Test account connection"`
|
|
Remove AuthRemoveCmd `cmd:"" help:"Remove an account"`
|
|
Password AuthPasswordCmd `cmd:"" help:"Set protocol-specific passwords"`
|
|
}
|
|
|
|
// AuthAddCmd adds a new account.
|
|
type AuthAddCmd struct {
|
|
Email string `arg:"" help:"Email address for the account"`
|
|
IMAPHost string `help:"IMAP server hostname" name:"imap-host"`
|
|
IMAPPort int `help:"IMAP server port" name:"imap-port" default:"993"`
|
|
SMTPHost string `help:"SMTP server hostname" name:"smtp-host"`
|
|
SMTPPort int `help:"SMTP server port" name:"smtp-port" default:"587"`
|
|
CalDAVURL string `help:"CalDAV server URL (e.g., https://caldav.example.com/)" name:"caldav-url"`
|
|
CardDAVURL string `help:"CardDAV server URL (e.g., https://carddav.example.com/)" name:"carddav-url"`
|
|
WebDAVURL string `help:"WebDAV server URL (e.g., https://webdav.example.com/)" name:"webdav-url"`
|
|
Password string `help:"Password (will prompt if not provided)"`
|
|
Discover bool `help:"Auto-discover servers from DNS"`
|
|
Insecure bool `help:"Skip TLS certificate verification"`
|
|
NoTLS bool `help:"Disable TLS (plain text connection)" name:"no-tls"`
|
|
Storage string `help:"Password storage: keychain or file" default:"keychain" enum:"keychain,file"`
|
|
}
|
|
|
|
// Run executes the auth add command.
|
|
func (c *AuthAddCmd) Run(root *Root) error {
|
|
// Set storage type
|
|
if c.Storage == "file" {
|
|
config.SetStorageType(config.StorageFile)
|
|
} else {
|
|
config.SetStorageType(config.StorageKeyring)
|
|
}
|
|
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
// Save storage preference
|
|
cfg.Storage = c.Storage
|
|
|
|
// Auto-discover if --discover flag set
|
|
if c.Discover {
|
|
fmt.Printf("Auto-discovering servers for %s...\n", c.Email)
|
|
result, err := discover.Discover(c.Email)
|
|
if err != nil {
|
|
return fmt.Errorf("auto-discover failed: %w", err)
|
|
}
|
|
if result.IMAP != nil && c.IMAPHost == "" {
|
|
c.IMAPHost = result.IMAP.Host
|
|
c.IMAPPort = result.IMAP.Port
|
|
fmt.Printf(" IMAP: %s:%d\n", c.IMAPHost, c.IMAPPort)
|
|
}
|
|
if result.SMTP != nil && c.SMTPHost == "" {
|
|
c.SMTPHost = result.SMTP.Host
|
|
c.SMTPPort = result.SMTP.Port
|
|
fmt.Printf(" SMTP: %s:%d\n", c.SMTPHost, c.SMTPPort)
|
|
}
|
|
}
|
|
|
|
// Validate required fields
|
|
if c.IMAPHost == "" {
|
|
return fmt.Errorf("--imap-host is required (or use --discover)")
|
|
}
|
|
if c.SMTPHost == "" {
|
|
return fmt.Errorf("--smtp-host is required (or use --discover)")
|
|
}
|
|
|
|
// TODO: Prompt for password if not provided
|
|
if c.Password == "" {
|
|
return fmt.Errorf("--password is required (keyring integration coming soon)")
|
|
}
|
|
|
|
acct := config.Account{
|
|
Email: c.Email,
|
|
IMAP: config.ServerConfig{
|
|
Host: c.IMAPHost,
|
|
Port: c.IMAPPort,
|
|
TLS: !c.NoTLS,
|
|
Insecure: c.Insecure,
|
|
NoTLS: c.NoTLS,
|
|
},
|
|
SMTP: config.ServerConfig{
|
|
Host: c.SMTPHost,
|
|
Port: c.SMTPPort,
|
|
TLS: !c.NoTLS,
|
|
StartTLS: !c.NoTLS,
|
|
Insecure: c.Insecure,
|
|
NoTLS: c.NoTLS,
|
|
},
|
|
CalDAV: config.CalDAVConfig{
|
|
URL: c.CalDAVURL,
|
|
},
|
|
CardDAV: config.CardDAVConfig{
|
|
URL: c.CardDAVURL,
|
|
},
|
|
WebDAV: config.WebDAVConfig{
|
|
URL: c.WebDAVURL,
|
|
},
|
|
}
|
|
|
|
if err := cfg.AddAccount(acct, c.Password); err != nil {
|
|
return fmt.Errorf("failed to add account: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Added account: %s (storage: %s)\n", c.Email, c.Storage)
|
|
return nil
|
|
}
|
|
|
|
// AuthListCmd lists configured accounts.
|
|
type AuthListCmd struct{}
|
|
|
|
// Run executes the auth list command.
|
|
func (c *AuthListCmd) Run(root *Root) error {
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
accounts := cfg.ListAccounts()
|
|
if len(accounts) == 0 {
|
|
fmt.Println("No accounts configured. Use 'sog auth add' to add one.")
|
|
return nil
|
|
}
|
|
|
|
for _, acct := range accounts {
|
|
marker := " "
|
|
if acct.Email == cfg.DefaultAccount {
|
|
marker = "* "
|
|
}
|
|
extras := ""
|
|
if acct.CalDAV.URL != "" {
|
|
extras += ", CalDAV: ✓"
|
|
}
|
|
if acct.CardDAV.URL != "" {
|
|
extras += ", CardDAV: ✓"
|
|
}
|
|
if acct.WebDAV.URL != "" {
|
|
extras += ", WebDAV: ✓"
|
|
}
|
|
fmt.Printf("%s%s (IMAP: %s:%d, SMTP: %s:%d%s)\n",
|
|
marker, acct.Email,
|
|
acct.IMAP.Host, acct.IMAP.Port,
|
|
acct.SMTP.Host, acct.SMTP.Port,
|
|
extras)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AuthTestCmd tests account connection.
|
|
type AuthTestCmd struct {
|
|
Email string `arg:"" optional:"" help:"Account to test (default: default account)"`
|
|
}
|
|
|
|
// Run executes the auth test command.
|
|
func (c *AuthTestCmd) Run(root *Root) error {
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
email := c.Email
|
|
if email == "" {
|
|
email = root.Account
|
|
}
|
|
if email == "" {
|
|
email = cfg.DefaultAccount
|
|
}
|
|
if email == "" {
|
|
return fmt.Errorf("no account specified and no default set")
|
|
}
|
|
|
|
acct, err := cfg.GetAccount(email)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
password, err := cfg.GetPassword(email)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get password: %w", err)
|
|
}
|
|
|
|
fmt.Printf("Testing %s...\n", email)
|
|
|
|
// Test IMAP
|
|
fmt.Printf(" IMAP %s:%d... ", acct.IMAP.Host, acct.IMAP.Port)
|
|
imapClient, err := imap.Connect(imap.Config{
|
|
Host: acct.IMAP.Host,
|
|
Port: acct.IMAP.Port,
|
|
TLS: acct.IMAP.TLS,
|
|
Insecure: acct.IMAP.Insecure,
|
|
NoTLS: acct.IMAP.NoTLS,
|
|
Email: email,
|
|
Password: password,
|
|
})
|
|
if err != nil {
|
|
fmt.Printf("FAILED: %v\n", err)
|
|
} else {
|
|
fmt.Println("OK")
|
|
imapClient.Close()
|
|
}
|
|
|
|
// Test SMTP
|
|
fmt.Printf(" SMTP %s:%d... ", acct.SMTP.Host, acct.SMTP.Port)
|
|
smtpClient := smtp.NewClient(smtp.Config{
|
|
Host: acct.SMTP.Host,
|
|
Port: acct.SMTP.Port,
|
|
TLS: acct.SMTP.TLS,
|
|
StartTLS: acct.SMTP.StartTLS,
|
|
Insecure: acct.SMTP.Insecure,
|
|
NoTLS: acct.SMTP.NoTLS,
|
|
Email: email,
|
|
Password: password,
|
|
})
|
|
if err := smtpClient.TestConnection(); err != nil {
|
|
fmt.Printf("FAILED: %v\n", err)
|
|
} else {
|
|
fmt.Println("OK")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AuthRemoveCmd removes an account.
|
|
type AuthRemoveCmd struct {
|
|
Email string `arg:"" help:"Account to remove"`
|
|
// Note: Uses global --force flag for confirmation skip
|
|
}
|
|
|
|
// Run executes the auth remove command.
|
|
func (c *AuthRemoveCmd) Run(root *Root) error {
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
// TODO: Confirmation prompt if not --force
|
|
|
|
if err := cfg.RemoveAccount(c.Email); err != nil {
|
|
return err
|
|
}
|
|
|
|
fmt.Printf("Removed account: %s\n", c.Email)
|
|
return nil
|
|
}
|
|
|
|
// AuthPasswordCmd sets protocol-specific passwords.
|
|
type AuthPasswordCmd struct {
|
|
Email string `arg:"" help:"Account email"`
|
|
IMAP string `help:"Password for IMAP" name:"imap"`
|
|
SMTP string `help:"Password for SMTP" name:"smtp"`
|
|
CalDAV string `help:"Password for CalDAV" name:"caldav"`
|
|
CardDAV string `help:"Password for CardDAV" name:"carddav"`
|
|
WebDAV string `help:"Password for WebDAV" name:"webdav"`
|
|
Default string `help:"Default password (used when protocol-specific not set)" name:"default"`
|
|
}
|
|
|
|
// Run executes the auth password command.
|
|
func (c *AuthPasswordCmd) Run(root *Root) error {
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load config: %w", err)
|
|
}
|
|
|
|
// Verify account exists
|
|
if _, err := cfg.GetAccount(c.Email); err != nil {
|
|
return err
|
|
}
|
|
|
|
var set []string
|
|
|
|
if c.Default != "" {
|
|
if err := config.SetPassword(c.Email, c.Default); err != nil {
|
|
return fmt.Errorf("failed to set default password: %w", err)
|
|
}
|
|
set = append(set, "default")
|
|
}
|
|
if c.IMAP != "" {
|
|
if err := config.SetPasswordForProtocol(c.Email, config.ProtocolIMAP, c.IMAP); err != nil {
|
|
return fmt.Errorf("failed to set IMAP password: %w", err)
|
|
}
|
|
set = append(set, "imap")
|
|
}
|
|
if c.SMTP != "" {
|
|
if err := config.SetPasswordForProtocol(c.Email, config.ProtocolSMTP, c.SMTP); err != nil {
|
|
return fmt.Errorf("failed to set SMTP password: %w", err)
|
|
}
|
|
set = append(set, "smtp")
|
|
}
|
|
if c.CalDAV != "" {
|
|
if err := config.SetPasswordForProtocol(c.Email, config.ProtocolCalDAV, c.CalDAV); err != nil {
|
|
return fmt.Errorf("failed to set CalDAV password: %w", err)
|
|
}
|
|
set = append(set, "caldav")
|
|
}
|
|
if c.CardDAV != "" {
|
|
if err := config.SetPasswordForProtocol(c.Email, config.ProtocolCardDAV, c.CardDAV); err != nil {
|
|
return fmt.Errorf("failed to set CardDAV password: %w", err)
|
|
}
|
|
set = append(set, "carddav")
|
|
}
|
|
if c.WebDAV != "" {
|
|
if err := config.SetPasswordForProtocol(c.Email, config.ProtocolWebDAV, c.WebDAV); err != nil {
|
|
return fmt.Errorf("failed to set WebDAV password: %w", err)
|
|
}
|
|
set = append(set, "webdav")
|
|
}
|
|
|
|
if len(set) == 0 {
|
|
return fmt.Errorf("no passwords specified. Use --default, --imap, --smtp, --caldav, --carddav, or --webdav")
|
|
}
|
|
|
|
fmt.Printf("Set passwords for %s: %v\n", c.Email, set)
|
|
return nil
|
|
}
|