feat: implement automatic auth token refresh on 401 with context support (FRE-4763)

- Add SessionRefresher interface for token refresh abstraction
- Update ProtonMailClient to auto-refresh on 401 responses
- Add DoWithContext method for context-aware HTTP requests
- Update SessionManager with RefreshTokenWithContext method
- Update LoginWithCredentials and LoginInteractive to accept context
- Add checkAuthenticatedWithManager helper for commands needing session manager
- All API methods now support proper cancellation via context.Context

Files changed:
- internal/api/client.go - Auto-refresh on 401, context support
- internal/auth/session.go - Context-aware refresh and login methods
- internal/auth/interface.go - SessionRefresher interface
- cmd/mail.go, cmd/draft.go, cmd/folders.go - Updated to use new helpers
- cmd/auth.go - Context support for login commands

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-05-09 21:46:03 -04:00
parent 19a9e2a3df
commit 691a2acdad
7 changed files with 125 additions and 35 deletions

View File

@@ -31,6 +31,22 @@ func checkAuthenticated() (*auth.Session, error) {
return session, nil
}
func checkAuthenticatedWithManager() (*auth.Session, *auth.SessionManager, error) {
sessionMgr, err := auth.NewSessionManager()
if err != nil {
return nil, nil, fmt.Errorf("failed to create session manager: %w", err)
}
authenticated, err := sessionMgr.IsAuthenticated()
if err != nil || !authenticated {
return nil, nil, fmt.Errorf("not authenticated (run 'pop login' first): %w", err)
}
session, err := sessionMgr.GetSession()
if err != nil {
return nil, nil, fmt.Errorf("not authenticated: %w", err)
}
return session, sessionMgr, nil
}
func mailCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "mail",
@@ -64,12 +80,12 @@ func mailListCmd() *cobra.Command {
return fmt.Errorf("failed to load config: %w", err)
}
session, err := checkAuthenticated()
session, sessionMgr, err := checkAuthenticatedWithManager()
if err != nil {
return err
}
client := api.NewProtonMailClient(cfg)
client := api.NewProtonMailClient(cfg, sessionMgr)
client.SetAuthHeader(session.AccessToken)
mailClient := internalmail.NewClient(client)
@@ -158,12 +174,12 @@ func mailReadCmd() *cobra.Command {
return fmt.Errorf("failed to load config: %w", err)
}
session, err := checkAuthenticated()
session, sessionMgr, err := checkAuthenticatedWithManager()
if err != nil {
return err
}
client := api.NewProtonMailClient(cfg)
client := api.NewProtonMailClient(cfg, sessionMgr)
client.SetAuthHeader(session.AccessToken)
mailClient := internalmail.NewClient(client)
@@ -221,12 +237,12 @@ func mailSendCmd() *cobra.Command {
return fmt.Errorf("failed to load config: %w", err)
}
session, err := checkAuthenticated()
session, sessionMgr, err := checkAuthenticatedWithManager()
if err != nil {
return err
}
client := api.NewProtonMailClient(cfg)
client := api.NewProtonMailClient(cfg, sessionMgr)
client.SetAuthHeader(session.AccessToken)
mailClient := internalmail.NewClient(client)
@@ -276,12 +292,12 @@ func mailDeleteCmd() *cobra.Command {
return fmt.Errorf("failed to load config: %w", err)
}
session, err := checkAuthenticated()
session, sessionMgr, err := checkAuthenticatedWithManager()
if err != nil {
return err
}
client := api.NewProtonMailClient(cfg)
client := api.NewProtonMailClient(cfg, sessionMgr)
client.SetAuthHeader(session.AccessToken)
mailClient := internalmail.NewClient(client)
@@ -312,12 +328,12 @@ func mailTrashCmd() *cobra.Command {
return fmt.Errorf("failed to load config: %w", err)
}
session, err := checkAuthenticated()
session, sessionMgr, err := checkAuthenticatedWithManager()
if err != nil {
return err
}
client := api.NewProtonMailClient(cfg)
client := api.NewProtonMailClient(cfg, sessionMgr)
client.SetAuthHeader(session.AccessToken)
mailClient := internalmail.NewClient(client)
@@ -456,12 +472,12 @@ func mailSearchCmd() *cobra.Command {
return fmt.Errorf("failed to load config: %w", err)
}
session, err := checkAuthenticated()
session, sessionMgr, err := checkAuthenticatedWithManager()
if err != nil {
return err
}
client := api.NewProtonMailClient(cfg)
client := api.NewProtonMailClient(cfg, sessionMgr)
client.SetAuthHeader(session.AccessToken)
mailClient := internalmail.NewClient(client)