fix: remove PGP encryption (#450)

Drew Smirnoff created

Change summary

config/config.go     | 103 ++++++++++++++++++++++-----------------------
main.go              |   2 
tui/composer.go      |  21 --------
tui/composer_test.go |  15 +-----
tui/messages.go      |   1 
tui/settings.go      |  30 +++----------
6 files changed, 62 insertions(+), 110 deletions(-)

Detailed changes

config/config.go 🔗

@@ -35,12 +35,11 @@ type Account struct {
 	SMIMESignByDefault bool   `json:"smime_sign_by_default,omitempty"` // Whether to enable S/MIME signing by default
 
 	// PGP settings
-	PGPPublicKey        string `json:"pgp_public_key,omitempty"`         // Path to public key (.asc or .gpg)
-	PGPPrivateKey       string `json:"pgp_private_key,omitempty"`        // Path to private key (.asc or .gpg)
-	PGPKeySource        string `json:"pgp_key_source,omitempty"`         // "file" (default) or "yubikey" for hardware key
-	PGPPIN              string `json:"-"`                                // YubiKey PIN (stored in keyring, not JSON)
-	PGPSignByDefault    bool   `json:"pgp_sign_by_default,omitempty"`    // Auto-sign outgoing emails
-	PGPEncryptByDefault bool   `json:"pgp_encrypt_by_default,omitempty"` // Auto-encrypt when recipient keys available
+	PGPPublicKey     string `json:"pgp_public_key,omitempty"`      // Path to public key (.asc or .gpg)
+	PGPPrivateKey    string `json:"pgp_private_key,omitempty"`     // Path to private key (.asc or .gpg)
+	PGPKeySource     string `json:"pgp_key_source,omitempty"`      // "file" (default) or "yubikey" for hardware key
+	PGPPIN           string `json:"-"`                             // YubiKey PIN (stored in keyring, not JSON)
+	PGPSignByDefault bool   `json:"pgp_sign_by_default,omitempty"` // Auto-sign outgoing emails
 
 	// OAuth2 settings
 	AuthMethod string `json:"auth_method,omitempty"` // "password" (default) or "oauth2"
@@ -210,30 +209,29 @@ func LoadConfig() (*Config, error) {
 	var needsMigration bool
 
 	type rawAccount struct {
-		ID                  string `json:"id"`
-		Name                string `json:"name"`
-		Email               string `json:"email"`
-		Password            string `json:"password,omitempty"`
-		ServiceProvider     string `json:"service_provider"`
-		FetchEmail          string `json:"fetch_email,omitempty"`
-		IMAPServer          string `json:"imap_server,omitempty"`
-		IMAPPort            int    `json:"imap_port,omitempty"`
-		SMTPServer          string `json:"smtp_server,omitempty"`
-		SMTPPort            int    `json:"smtp_port,omitempty"`
-		Insecure            bool   `json:"insecure,omitempty"`
-		SMIMECert           string `json:"smime_cert,omitempty"`
-		SMIMEKey            string `json:"smime_key,omitempty"`
-		SMIMESignByDefault  bool   `json:"smime_sign_by_default,omitempty"`
-		PGPPublicKey        string `json:"pgp_public_key,omitempty"`
-		PGPPrivateKey       string `json:"pgp_private_key,omitempty"`
-		PGPKeySource        string `json:"pgp_key_source,omitempty"`
-		PGPSignByDefault    bool   `json:"pgp_sign_by_default,omitempty"`
-		PGPEncryptByDefault bool   `json:"pgp_encrypt_by_default,omitempty"`
-		AuthMethod          string `json:"auth_method,omitempty"`
-		Protocol            string `json:"protocol,omitempty"`
-		JMAPEndpoint        string `json:"jmap_endpoint,omitempty"`
-		POP3Server          string `json:"pop3_server,omitempty"`
-		POP3Port            int    `json:"pop3_port,omitempty"`
+		ID                 string `json:"id"`
+		Name               string `json:"name"`
+		Email              string `json:"email"`
+		Password           string `json:"password,omitempty"`
+		ServiceProvider    string `json:"service_provider"`
+		FetchEmail         string `json:"fetch_email,omitempty"`
+		IMAPServer         string `json:"imap_server,omitempty"`
+		IMAPPort           int    `json:"imap_port,omitempty"`
+		SMTPServer         string `json:"smtp_server,omitempty"`
+		SMTPPort           int    `json:"smtp_port,omitempty"`
+		Insecure           bool   `json:"insecure,omitempty"`
+		SMIMECert          string `json:"smime_cert,omitempty"`
+		SMIMEKey           string `json:"smime_key,omitempty"`
+		SMIMESignByDefault bool   `json:"smime_sign_by_default,omitempty"`
+		PGPPublicKey       string `json:"pgp_public_key,omitempty"`
+		PGPPrivateKey      string `json:"pgp_private_key,omitempty"`
+		PGPKeySource       string `json:"pgp_key_source,omitempty"`
+		PGPSignByDefault   bool   `json:"pgp_sign_by_default,omitempty"`
+		AuthMethod         string `json:"auth_method,omitempty"`
+		Protocol           string `json:"protocol,omitempty"`
+		JMAPEndpoint       string `json:"jmap_endpoint,omitempty"`
+		POP3Server         string `json:"pop3_server,omitempty"`
+		POP3Port           int    `json:"pop3_port,omitempty"`
 	}
 	type diskConfig struct {
 		Accounts             []rawAccount  `json:"accounts"`
@@ -276,29 +274,28 @@ func LoadConfig() (*Config, error) {
 	config.MailingLists = raw.MailingLists
 	for _, rawAcc := range raw.Accounts {
 		acc := Account{
-			ID:                  rawAcc.ID,
-			Name:                rawAcc.Name,
-			Email:               rawAcc.Email,
-			ServiceProvider:     rawAcc.ServiceProvider,
-			FetchEmail:          rawAcc.FetchEmail,
-			IMAPServer:          rawAcc.IMAPServer,
-			IMAPPort:            rawAcc.IMAPPort,
-			SMTPServer:          rawAcc.SMTPServer,
-			SMTPPort:            rawAcc.SMTPPort,
-			Insecure:            rawAcc.Insecure,
-			SMIMECert:           rawAcc.SMIMECert,
-			SMIMEKey:            rawAcc.SMIMEKey,
-			SMIMESignByDefault:  rawAcc.SMIMESignByDefault,
-			PGPPublicKey:        rawAcc.PGPPublicKey,
-			PGPPrivateKey:       rawAcc.PGPPrivateKey,
-			PGPKeySource:        rawAcc.PGPKeySource,
-			PGPSignByDefault:    rawAcc.PGPSignByDefault,
-			PGPEncryptByDefault: rawAcc.PGPEncryptByDefault,
-			AuthMethod:          rawAcc.AuthMethod,
-			Protocol:            rawAcc.Protocol,
-			JMAPEndpoint:        rawAcc.JMAPEndpoint,
-			POP3Server:          rawAcc.POP3Server,
-			POP3Port:            rawAcc.POP3Port,
+			ID:                 rawAcc.ID,
+			Name:               rawAcc.Name,
+			Email:              rawAcc.Email,
+			ServiceProvider:    rawAcc.ServiceProvider,
+			FetchEmail:         rawAcc.FetchEmail,
+			IMAPServer:         rawAcc.IMAPServer,
+			IMAPPort:           rawAcc.IMAPPort,
+			SMTPServer:         rawAcc.SMTPServer,
+			SMTPPort:           rawAcc.SMTPPort,
+			Insecure:           rawAcc.Insecure,
+			SMIMECert:          rawAcc.SMIMECert,
+			SMIMEKey:           rawAcc.SMIMEKey,
+			SMIMESignByDefault: rawAcc.SMIMESignByDefault,
+			PGPPublicKey:       rawAcc.PGPPublicKey,
+			PGPPrivateKey:      rawAcc.PGPPrivateKey,
+			PGPKeySource:       rawAcc.PGPKeySource,
+			PGPSignByDefault:   rawAcc.PGPSignByDefault,
+			AuthMethod:         rawAcc.AuthMethod,
+			Protocol:           rawAcc.Protocol,
+			JMAPEndpoint:       rawAcc.JMAPEndpoint,
+			POP3Server:         rawAcc.POP3Server,
+			POP3Port:           rawAcc.POP3Port,
 		}
 
 		if rawAcc.Password != "" {

main.go 🔗

@@ -1662,7 +1662,7 @@ func sendEmail(account *config.Account, msg tui.SendEmailMsg) tea.Cmd {
 			attachments[filename] = fileData
 		}
 
-		err := sender.SendEmail(account, recipients, cc, bcc, msg.Subject, body, string(htmlBody), images, attachments, msg.InReplyTo, msg.References, msg.SignSMIME, msg.EncryptSMIME, msg.SignPGP, msg.EncryptPGP)
+		err := sender.SendEmail(account, recipients, cc, bcc, msg.Subject, body, string(htmlBody), images, attachments, msg.InReplyTo, msg.References, msg.SignSMIME, msg.EncryptSMIME, msg.SignPGP, false)
 		if err != nil {
 			log.Printf("Failed to send email: %v", err)
 			return tui.EmailResultMsg{Err: err}

tui/composer.go 🔗

@@ -43,7 +43,6 @@ const (
 	focusSignature
 	focusAttachment
 	focusEncryptSMIME
-	focusEncryptPGP
 	focusSend
 )
 
@@ -58,7 +57,6 @@ type Composer struct {
 	signatureInput  textarea.Model
 	attachmentPaths []string
 	encryptSMIME    bool
-	encryptPGP      bool
 	width           int
 	height          int
 	confirmingExit  bool
@@ -404,11 +402,7 @@ func (m *Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 					m.encryptSMIME = !m.encryptSMIME
 				}
 				return m, nil
-			case focusEncryptPGP:
-				if msg.String() == "enter" || msg.String() == " " {
-					m.encryptPGP = !m.encryptPGP
-				}
-				return m, nil
+
 			case focusSend:
 				if msg.String() == "enter" {
 					acc := m.getSelectedAccount()
@@ -432,7 +426,6 @@ func (m *Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 							SignSMIME:       acc != nil && acc.SMIMESignByDefault,
 							EncryptSMIME:    m.encryptSMIME,
 							SignPGP:         acc != nil && acc.PGPSignByDefault,
-							EncryptPGP:      m.encryptPGP,
 						}
 					}
 				}
@@ -538,15 +531,6 @@ func (m *Composer) View() tea.View {
 		encField = focusedStyle.Render(fmt.Sprintf("> Encrypt Email (S/MIME): %s", encToggle))
 	}
 
-	pgpEncToggle := "[ ]"
-	if m.encryptPGP {
-		pgpEncToggle = "[x]"
-	}
-	pgpEncField := blurredStyle.Render(fmt.Sprintf("  Encrypt Email (PGP): %s", pgpEncToggle))
-	if m.focusIndex == focusEncryptPGP {
-		pgpEncField = focusedStyle.Render(fmt.Sprintf("> Encrypt Email (PGP): %s", pgpEncToggle))
-	}
-
 	// Build To field with suggestions
 	toFieldView := m.toInput.View()
 	if m.showSuggestions && len(m.suggestions) > 0 {
@@ -593,8 +577,6 @@ func (m *Composer) View() tea.View {
 		tip = "Enter: add file • backspace/d: remove last attachment"
 	case focusEncryptSMIME:
 		tip = "Press Space or Enter to toggle S/MIME encryption on or off."
-	case focusEncryptPGP:
-		tip = "Press Space or Enter to toggle PGP encryption on or off."
 	case focusSend:
 		tip = "Press Enter to send the email."
 	}
@@ -611,7 +593,6 @@ func (m *Composer) View() tea.View {
 		m.signatureInput.View(),
 		attachmentStyle.Render(attachmentField),
 		smimeToggleStyle.Render(encField),
-		smimeToggleStyle.Render(pgpEncField),
 		button,
 		"",
 	}

tui/composer_test.go 🔗

@@ -71,18 +71,11 @@ func TestComposerUpdate(t *testing.T) {
 			t.Errorf("After seven Tabs, focusIndex should be %d (focusEncryptSMIME), got %d", focusEncryptSMIME, composer.focusIndex)
 		}
 
-		// Simulate pressing Tab again to move to the 'EncryptPGP' toggle.
-		model, _ = composer.Update(tea.KeyPressMsg{Code: tea.KeyTab})
-		composer = model.(*Composer)
-		if composer.focusIndex != focusEncryptPGP {
-			t.Errorf("After eight Tabs, focusIndex should be %d (focusEncryptPGP), got %d", focusEncryptPGP, composer.focusIndex)
-		}
-
 		// Simulate pressing Tab again to move to the 'Send' button.
 		model, _ = composer.Update(tea.KeyPressMsg{Code: tea.KeyTab})
 		composer = model.(*Composer)
 		if composer.focusIndex != focusSend {
-			t.Errorf("After nine Tabs, focusIndex should be %d (focusSend), got %d", focusSend, composer.focusIndex)
+			t.Errorf("After eight Tabs, focusIndex should be %d (focusSend), got %d", focusSend, composer.focusIndex)
 		}
 
 		// Simulate one more Tab to wrap around.
@@ -90,7 +83,7 @@ func TestComposerUpdate(t *testing.T) {
 		model, _ = composer.Update(tea.KeyPressMsg{Code: tea.KeyTab})
 		composer = model.(*Composer)
 		if composer.focusIndex != focusTo {
-			t.Errorf("After ten Tabs, focusIndex should wrap to %d (focusTo) since single account skips From, got %d", focusTo, composer.focusIndex)
+			t.Errorf("After nine Tabs, focusIndex should wrap to %d (focusTo) since single account skips From, got %d", focusTo, composer.focusIndex)
 		}
 	})
 
@@ -218,9 +211,7 @@ func TestComposerUpdate(t *testing.T) {
 		multiComposer = model.(*Composer)
 		model, _ = multiComposer.Update(tea.KeyPressMsg{Code: tea.KeyTab}) // Attachment -> EncryptSMIME
 		multiComposer = model.(*Composer)
-		model, _ = multiComposer.Update(tea.KeyPressMsg{Code: tea.KeyTab}) // EncryptSMIME -> EncryptPGP
-		multiComposer = model.(*Composer)
-		model, _ = multiComposer.Update(tea.KeyPressMsg{Code: tea.KeyTab}) // EncryptPGP -> Send
+		model, _ = multiComposer.Update(tea.KeyPressMsg{Code: tea.KeyTab}) // EncryptSMIME -> Send
 		multiComposer = model.(*Composer)
 		model, _ = multiComposer.Update(tea.KeyPressMsg{Code: tea.KeyTab}) // Send -> From (wrap)
 		multiComposer = model.(*Composer)

tui/messages.go 🔗

@@ -36,7 +36,6 @@ type SendEmailMsg struct {
 	SignSMIME       bool   // Whether to sign the email using S/MIME
 	EncryptSMIME    bool   // Whether to encrypt the email using S/MIME
 	SignPGP         bool   // Whether to sign the email using PGP
-	EncryptPGP      bool   // Whether to encrypt the email using PGP
 }
 
 type Credentials struct {

tui/settings.go 🔗

@@ -312,10 +312,9 @@ func (m *Settings) updateAccounts(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
 // 5: PGP Key Source toggle (file/yubikey)
 // 6: PGP PIN input (only shown if yubikey)
 // 7: PGP Sign By Default toggle
-// 8: PGP Encrypt By Default toggle
-// 9: Save button
-// 10: Cancel button
-const cryptoConfigMaxFocus = 10
+// 8: Save button
+// 9: Cancel button
+const cryptoConfigMaxFocus = 9
 
 func (m *Settings) updateSMIMEConfig(msg tea.KeyPressMsg) (*Settings, tea.Cmd) {
 	var cmds []tea.Cmd
@@ -410,12 +409,7 @@ func (m *Settings) updateSMIMEConfig(msg tea.KeyPressMsg) (*Settings, tea.Cmd) {
 				m.cfg.Accounts[m.editingAccountIdx].PGPSignByDefault = !m.cfg.Accounts[m.editingAccountIdx].PGPSignByDefault
 			}
 			return m, nil
-		case 8: // PGP encrypt toggle
-			if msg.String() == "enter" || msg.String() == " " {
-				m.cfg.Accounts[m.editingAccountIdx].PGPEncryptByDefault = !m.cfg.Accounts[m.editingAccountIdx].PGPEncryptByDefault
-			}
-			return m, nil
-		case 9: // Save
+		case 8: // Save
 			if msg.String() == "enter" {
 				m.cfg.Accounts[m.editingAccountIdx].SMIMECert = m.smimeCertInput.Value()
 				m.cfg.Accounts[m.editingAccountIdx].SMIMEKey = m.smimeKeyInput.Value()
@@ -427,7 +421,7 @@ func (m *Settings) updateSMIMEConfig(msg tea.KeyPressMsg) (*Settings, tea.Cmd) {
 				m.state = SettingsAccounts
 				return m, nil
 			}
-		case 10: // Cancel
+		case 9: // Cancel
 			if msg.String() == "enter" {
 				m.state = SettingsAccounts
 				return m, nil
@@ -806,27 +800,17 @@ func (m *Settings) viewSMIMEConfig() string {
 		b.WriteString(settingsBlurredStyle.Render(fmt.Sprintf("  Sign By Default: %s\n\n", pgpSignStatus)))
 	}
 
-	pgpEncryptStatus := "OFF"
-	if account.PGPEncryptByDefault {
-		pgpEncryptStatus = "ON"
-	}
-	if m.focusIndex == 8 {
-		b.WriteString(settingsFocusedStyle.Render(fmt.Sprintf("> Encrypt By Default: %s\n\n", pgpEncryptStatus)))
-	} else {
-		b.WriteString(settingsBlurredStyle.Render(fmt.Sprintf("  Encrypt By Default: %s\n\n", pgpEncryptStatus)))
-	}
-
 	// --- Buttons ---
 	saveBtn := "[ Save ]"
 	cancelBtn := "[ Cancel ]"
 
-	if m.focusIndex == 9 {
+	if m.focusIndex == 8 {
 		saveBtn = settingsFocusedStyle.Render(saveBtn)
 	} else {
 		saveBtn = settingsBlurredStyle.Render(saveBtn)
 	}
 
-	if m.focusIndex == 10 {
+	if m.focusIndex == 9 {
 		cancelBtn = settingsFocusedStyle.Render(cancelBtn)
 	} else {
 		cancelBtn = settingsBlurredStyle.Render(cancelBtn)