From cfe2e125ecb06071470edf29efa551153b68fa5c Mon Sep 17 00:00:00 2001 From: alexopenline Date: Sun, 13 Apr 2025 17:02:06 +0300 Subject: [PATCH 1/3] add needs manual refresh property --- .env | 2 +- interfaces/mailbox_repository.go | 1 + internal/models/mailbox.go | 19 +++++++++++-------- internal/repository/mailbox.go | 19 +++++++++++++++++++ services/google/service.go | 19 +++++++++++++++++++ 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/.env b/.env index bfedf7a..ec8c106 100644 --- a/.env +++ b/.env @@ -54,4 +54,4 @@ CRON_DISABLE=true GOOGLE_OAUTH_CLIENT_ID=tbd GOOGLE_OAUTH_CLIENT_SECRET=tbd -GOOGLE_OAUTH_ENCRYPTION_KEY=tbd \ No newline at end of file +EMAIL_ENCRYPTION_KEY=tbd \ No newline at end of file diff --git a/interfaces/mailbox_repository.go b/interfaces/mailbox_repository.go index 6b18780..de44404 100644 --- a/interfaces/mailbox_repository.go +++ b/interfaces/mailbox_repository.go @@ -25,4 +25,5 @@ type MailboxRepository interface { GetForRampUp(ctx context.Context) ([]*models.Mailbox, error) UpdateRampUpFields(ctx context.Context, mailbox *models.Mailbox) error UpdateOauthToken(ctx context.Context, mailboxID, accessToken, refreshToken string, tokenExpiry *time.Time) error + MarkForManualRefresh(ctx context.Context, mailboxID string, needsManualRefresh bool) error } diff --git a/internal/models/mailbox.go b/internal/models/mailbox.go index 547deef..385f2ec 100644 --- a/internal/models/mailbox.go +++ b/internal/models/mailbox.go @@ -6,9 +6,10 @@ import ( "crypto/cipher" "crypto/rand" "encoding/base64" - "github.com/pkg/errors" "time" + "github.com/pkg/errors" + "github.com/lib/pq" "gorm.io/gorm" @@ -66,13 +67,15 @@ type Mailbox struct { SmtpSecurity enum.EmailSecurity `gorm:"column:smtp_security;type:varchar(50)" json:"smtpSecurity"` // OAuth specific fields (for Google, Microsoft, etc.) - OAuthClientID string `gorm:"column:oauth_client_id;type:varchar(255)" json:"oauthClientId"` - OAuthClientSecret string `gorm:"column:oauth_client_secret;type:varchar(255)" json:"oauthClientSecret"` - OAuthRefreshToken string `gorm:"column:oauth_refresh_token;type:varchar(1000)" json:"oauthRefreshToken"` - OAuthAccessToken string `gorm:"column:oauth_access_token;type:varchar(1000)" json:"oauthAccessToken"` - OAuthTokenExpiry *time.Time `gorm:"column:oauth_token_expiry;type:timestamp" json:"oauthTokenExpiry"` - OAuthScope string `gorm:"column:oauth_scope;type:varchar(2000)" json:"oauthScope"` - OAuthTokenId string `gorm:"column:oauth_token_id;type:varchar(2000)" json:"oauthTokenId"` + OAuthClientID string `gorm:"column:oauth_client_id;type:varchar(255)" json:"oauthClientId"` + OAuthClientSecret string `gorm:"column:oauth_client_secret;type:varchar(255)" json:"oauthClientSecret"` + OAuthRefreshToken string `gorm:"column:oauth_refresh_token;type:varchar(1000)" json:"oauthRefreshToken"` + OAuthAccessToken string `gorm:"column:oauth_access_token;type:varchar(1000)" json:"oauthAccessToken"` + OAuthTokenExpiry *time.Time `gorm:"column:oauth_token_expiry;type:timestamp" json:"oauthTokenExpiry"` + OAuthScope string `gorm:"column:oauth_scope;type:varchar(2000)" json:"oauthScope"` + OAuthTokenId string `gorm:"column:oauth_token_id;type:varchar(2000)" json:"oauthTokenId"` + OAuthNeedsManualRefresh bool `gorm:"column:oauth_needs_manual_refresh;default:false;"` + OAuthRevokedAt *time.Time `gorm:"column:oauth_revoked_at;type:timestamp" json:"oauthRevokedAt"` // Email sending configuration ReplyToAddress string `gorm:"column:reply_to_address;type:varchar(255)" json:"replyToAddress"` diff --git a/internal/repository/mailbox.go b/internal/repository/mailbox.go index b6c5075..698cb2a 100644 --- a/internal/repository/mailbox.go +++ b/internal/repository/mailbox.go @@ -346,3 +346,22 @@ func (r *mailboxRepository) UpdateOauthToken(ctx context.Context, mailboxID, acc return nil } + +func (r *mailboxRepository) MarkForManualRefresh(ctx context.Context, mailboxID string, needsManualRefresh bool) error { + spans, ctx := telemetry.StartPostgresSpan(ctx, "mailboxRepository.MarkForManualRefresh") + defer spans.Finish() + spans.TagEntity(mailboxID) + spans.LogKV("needsManualRefresh", needsManualRefresh) + + err := r.db.WithContext(ctx). + Model(&models.Mailbox{}). + Where("id = ?", mailboxID). + Update("oauth_needs_manual_refresh", needsManualRefresh).Error + + if err != nil { + spans.TraceError(err) + return err + } + + return nil +} diff --git a/services/google/service.go b/services/google/service.go index faf887e..1a26efc 100644 --- a/services/google/service.go +++ b/services/google/service.go @@ -2,6 +2,7 @@ package google import ( "context" + "errors" "fmt" "time" @@ -45,6 +46,12 @@ func (s *googleService) RefreshTokenIfNeeded(ctx context.Context, mailbox *model defer spans.Finish() spans.TagEntity(mailbox.ID) + if mailbox.OAuthRevokedAt != nil { + err := errors.New("mailbox is revoked") + spans.TraceError(err) + return err + } + // Check if token needs refresh (expires in less than 5 minutes) if mailbox.OAuthTokenExpiry != nil && mailbox.OAuthTokenExpiry.After(time.Now().Add(5*time.Minute)) { return nil // Token is still valid @@ -78,6 +85,11 @@ func (s *googleService) RefreshToken(ctx context.Context, mailbox *models.Mailbo newToken, err := tokenSource.Token() if err != nil { spans.TraceError(err) + err = s.repositories.MailboxRepository.MarkForManualRefresh(ctx, mailbox.ID) + if err != nil { + spans.TraceError(err) + return fmt.Errorf("failed to mark mailbox for manual refresh: %w", err) + } return fmt.Errorf("failed to refresh token: %w", err) } @@ -106,6 +118,13 @@ func (s *googleService) RefreshToken(ctx context.Context, mailbox *models.Mailbo return fmt.Errorf("failed to update mailbox with new token: %w", err) } + if mailbox.OAuthNeedsManualRefresh { + err = s.repositories.MailboxRepository.MarkForManualRefresh(ctx, mailbox.ID, false) + if err != nil { + spans.TraceError(err) + } + } + return nil } From e83d921515fb3e3434a0fa0cac8addc49bfeac70 Mon Sep 17 00:00:00 2001 From: alexopenline Date: Sun, 13 Apr 2025 17:03:08 +0300 Subject: [PATCH 2/3] fix --- services/google/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/google/service.go b/services/google/service.go index 1a26efc..3826fb1 100644 --- a/services/google/service.go +++ b/services/google/service.go @@ -85,7 +85,7 @@ func (s *googleService) RefreshToken(ctx context.Context, mailbox *models.Mailbo newToken, err := tokenSource.Token() if err != nil { spans.TraceError(err) - err = s.repositories.MailboxRepository.MarkForManualRefresh(ctx, mailbox.ID) + err = s.repositories.MailboxRepository.MarkForManualRefresh(ctx, mailbox.ID, true) if err != nil { spans.TraceError(err) return fmt.Errorf("failed to mark mailbox for manual refresh: %w", err) From 3e77eef50e756eb779b14a162ebfb4013e948285 Mon Sep 17 00:00:00 2001 From: alexopenline Date: Sun, 13 Apr 2025 17:14:11 +0300 Subject: [PATCH 3/3] fix --- services/imap/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/imap/service.go b/services/imap/service.go index c26d4b0..c622256 100644 --- a/services/imap/service.go +++ b/services/imap/service.go @@ -301,7 +301,7 @@ func (s *IMAPService) getConnectedClient(ctx context.Context, mailboxID string) func (s *IMAPService) runSingleMailbox(ctx context.Context, mailboxID string, config *models.Mailbox) { spans, ctx := telemetry.StartServiceSpan(ctx, "IMAPService.runSingleMailbox", telemetry.WithNewRoot()) defer spans.Finish() - spans.TagString("mailbox_id", mailboxID) + spans.TagEntity(mailboxID) spans.LogObjectAsJson("mailbox", config) s.wg.Add(1)