FRE-681: Fix security review findings (3 HIGH, 3 MEDIUM, 2 LOW)
HIGH fixes: - Access Token now used as PGP Passphrase: replaced session.AccessToken with session.MailPassphrase for all PGP operations - Session stored encrypted in keyring and file (was plain JSON) - Added checkAuthenticated() helper with IsAuthenticated() guard MEDIUM fixes: - Added MailPassphrase field to Session, collected during login - Added email validation in LoginInteractive - Added keyring cleanup on Logout - Implemented RefreshToken with actual API call LOW fixes: - Added mutex to PGPKeyRing for thread safety - Added ZeroPrivateKeyData() for memory cleanup - Use net/mail.ParseAddress for proper recipient parsing - Renamed internal/mail import to internalmail to avoid conflict
This commit is contained in:
64
cmd/draft.go
64
cmd/draft.go
@@ -6,9 +6,8 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/frenocorp/pop/internal/api"
|
||||
"github.com/frenocorp/pop/internal/auth"
|
||||
"github.com/frenocorp/pop/internal/config"
|
||||
"github.com/frenocorp/pop/internal/mail"
|
||||
internalmail "github.com/frenocorp/pop/internal/mail"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -49,12 +48,12 @@ func draftSaveCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
recipients := parseRecipients(to)
|
||||
var ccRecipients []mail.Recipient
|
||||
var ccRecipients []internalmail.Recipient
|
||||
if cc != "" {
|
||||
ccRecipients = parseRecipients(cc)
|
||||
}
|
||||
|
||||
var bccRecipients []mail.Recipient
|
||||
var bccRecipients []internalmail.Recipient
|
||||
if bcc != "" {
|
||||
bccRecipients = parseRecipients(bcc)
|
||||
}
|
||||
@@ -65,20 +64,16 @@ func draftSaveCmd() *cobra.Command {
|
||||
return fmt.Errorf("failed to load config: %w", err)
|
||||
}
|
||||
|
||||
sessionMgr, err := auth.NewSessionManager()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create session manager: %w", err)
|
||||
}
|
||||
session, err := sessionMgr.GetSession()
|
||||
session, err := checkAuthenticated()
|
||||
if err != nil {
|
||||
return fmt.Errorf("not authenticated: %w", err)
|
||||
}
|
||||
|
||||
client := api.NewProtonMailClient(cfg)
|
||||
client.SetAuthHeader(session.AccessToken)
|
||||
mailClient := mail.NewClient(client)
|
||||
mailClient := internalmail.NewClient(client)
|
||||
|
||||
draft := mail.Draft{
|
||||
draft := internalmail.Draft{
|
||||
To: recipients,
|
||||
CC: ccRecipients,
|
||||
BCC: bccRecipients,
|
||||
@@ -86,7 +81,7 @@ func draftSaveCmd() *cobra.Command {
|
||||
Body: msgBody,
|
||||
}
|
||||
|
||||
messageID, err := mailClient.SaveDraft(draft, session.AccessToken)
|
||||
messageID, err := mailClient.SaveDraft(draft, session.MailPassphrase)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save draft: %w", err)
|
||||
}
|
||||
@@ -130,20 +125,16 @@ func draftListCmd() *cobra.Command {
|
||||
return fmt.Errorf("failed to load config: %w", err)
|
||||
}
|
||||
|
||||
sessionMgr, err := auth.NewSessionManager()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create session manager: %w", err)
|
||||
}
|
||||
session, err := sessionMgr.GetSession()
|
||||
session, err := checkAuthenticated()
|
||||
if err != nil {
|
||||
return fmt.Errorf("not authenticated: %w", err)
|
||||
}
|
||||
|
||||
client := api.NewProtonMailClient(cfg)
|
||||
client.SetAuthHeader(session.AccessToken)
|
||||
mailClient := mail.NewClient(client)
|
||||
mailClient := internalmail.NewClient(client)
|
||||
|
||||
result, err := mailClient.ListDrafts(pageVal, pageSizeVal, session.AccessToken)
|
||||
result, err := mailClient.ListDrafts(pageVal, pageSizeVal, session.MailPassphrase)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list drafts: %w", err)
|
||||
}
|
||||
@@ -159,7 +150,7 @@ func draftListCmd() *cobra.Command {
|
||||
}
|
||||
|
||||
func draftEditCmd() *cobra.Command {
|
||||
var to, cc, subject, bodyFile, body string
|
||||
var to, cc, bcc, subject, bodyFile, body string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "edit <draft-id>",
|
||||
@@ -169,16 +160,21 @@ func draftEditCmd() *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
messageID := args[0]
|
||||
|
||||
var recipients []mail.Recipient
|
||||
var recipients []internalmail.Recipient
|
||||
if to != "" {
|
||||
recipients = parseRecipients(to)
|
||||
}
|
||||
|
||||
var ccRecipients []mail.Recipient
|
||||
var ccRecipients []internalmail.Recipient
|
||||
if cc != "" {
|
||||
ccRecipients = parseRecipients(cc)
|
||||
}
|
||||
|
||||
var bccRecipients []internalmail.Recipient
|
||||
if bcc != "" {
|
||||
bccRecipients = parseRecipients(bcc)
|
||||
}
|
||||
|
||||
msgBody := body
|
||||
if bodyFile != "" {
|
||||
data, err := os.ReadFile(bodyFile)
|
||||
@@ -194,27 +190,24 @@ func draftEditCmd() *cobra.Command {
|
||||
return fmt.Errorf("failed to load config: %w", err)
|
||||
}
|
||||
|
||||
sessionMgr, err := auth.NewSessionManager()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create session manager: %w", err)
|
||||
}
|
||||
session, err := sessionMgr.GetSession()
|
||||
session, err := checkAuthenticated()
|
||||
if err != nil {
|
||||
return fmt.Errorf("not authenticated: %w", err)
|
||||
}
|
||||
|
||||
client := api.NewProtonMailClient(cfg)
|
||||
client.SetAuthHeader(session.AccessToken)
|
||||
mailClient := mail.NewClient(client)
|
||||
mailClient := internalmail.NewClient(client)
|
||||
|
||||
draft := mail.Draft{
|
||||
draft := internalmail.Draft{
|
||||
To: recipients,
|
||||
CC: ccRecipients,
|
||||
BCC: bccRecipients,
|
||||
Subject: subject,
|
||||
Body: msgBody,
|
||||
}
|
||||
|
||||
if err := mailClient.UpdateDraft(messageID, draft, session.AccessToken); err != nil {
|
||||
if err := mailClient.UpdateDraft(messageID, draft, session.MailPassphrase); err != nil {
|
||||
return fmt.Errorf("failed to update draft: %w", err)
|
||||
}
|
||||
|
||||
@@ -225,6 +218,7 @@ func draftEditCmd() *cobra.Command {
|
||||
|
||||
cmd.Flags().StringVarP(&to, "to", "t", "", "New recipient addresses (comma-separated)")
|
||||
cmd.Flags().StringVarP(&cc, "cc", "c", "", "New CC addresses (comma-separated)")
|
||||
cmd.Flags().StringVarP(&bcc, "bcc", "b", "", "New BCC addresses (comma-separated)")
|
||||
cmd.Flags().StringVarP(&subject, "subject", "s", "", "New draft subject")
|
||||
cmd.Flags().StringVarP(&bodyFile, "body-file", "f", "", "File containing new draft body")
|
||||
cmd.Flags().StringVar(&body, "body", "", "New inline draft body")
|
||||
@@ -247,20 +241,16 @@ func draftSendCmd() *cobra.Command {
|
||||
return fmt.Errorf("failed to load config: %w", err)
|
||||
}
|
||||
|
||||
sessionMgr, err := auth.NewSessionManager()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create session manager: %w", err)
|
||||
}
|
||||
session, err := sessionMgr.GetSession()
|
||||
session, err := checkAuthenticated()
|
||||
if err != nil {
|
||||
return fmt.Errorf("not authenticated: %w", err)
|
||||
}
|
||||
|
||||
client := api.NewProtonMailClient(cfg)
|
||||
client.SetAuthHeader(session.AccessToken)
|
||||
mailClient := mail.NewClient(client)
|
||||
mailClient := internalmail.NewClient(client)
|
||||
|
||||
if err := mailClient.SendDraft(messageID, session.AccessToken); err != nil {
|
||||
if err := mailClient.SendDraft(messageID, session.MailPassphrase); err != nil {
|
||||
return fmt.Errorf("failed to send draft: %w", err)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user