Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,4 @@ CRON_DISABLE=true

GOOGLE_OAUTH_CLIENT_ID=tbd
GOOGLE_OAUTH_CLIENT_SECRET=tbd
GOOGLE_OAUTH_ENCRYPTION_KEY=tbd
EMAIL_ENCRYPTION_KEY=tbd
1 change: 1 addition & 0 deletions interfaces/mailbox_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
19 changes: 11 additions & 8 deletions internal/models/mailbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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"`
Expand Down
19 changes: 19 additions & 0 deletions internal/repository/mailbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
19 changes: 19 additions & 0 deletions services/google/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package google

import (
"context"
"errors"
"fmt"
"time"

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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, true)
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)
}

Expand Down Expand Up @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion services/imap/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down