forked from Silverfish/proton-bridge
The IMAP service no longer blocks the event loop from progressing. When syncing the event loop continues as usual, allow other parts of the bridge to continue updating. Once the sync is complete, it resets the event id back to the last handled id before sync started and then we play catch up.
705 lines
19 KiB
Go
705 lines
19 KiB
Go
// Copyright (c) 2023 Proton AG
|
|
//
|
|
// This file is part of Proton Mail Bridge.
|
|
//
|
|
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package user
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/ProtonMail/gluon/async"
|
|
"github.com/ProtonMail/gluon/reporter"
|
|
"github.com/ProtonMail/go-proton-api"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/configstatus"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/safe"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/orderedtasks"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/sendrecorder"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/smtp"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/syncservice"
|
|
telemetryservice "github.com/ProtonMail/proton-bridge/v3/internal/services/telemetry"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/userevents"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/services/useridentity"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/telemetry"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/usertypes"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
|
"github.com/ProtonMail/proton-bridge/v3/pkg/algo"
|
|
"github.com/bradenaw/juniper/xslices"
|
|
"github.com/go-resty/resty/v2"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/exp/maps"
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
var (
|
|
EventPeriod = 20 * time.Second // nolint:gochecknoglobals,revive
|
|
EventJitter = 20 * time.Second // nolint:gochecknoglobals,revive
|
|
)
|
|
|
|
const (
|
|
SyncRetryCooldown = 20 * time.Second
|
|
)
|
|
|
|
type User struct {
|
|
id string
|
|
log *logrus.Entry
|
|
|
|
vault *vault.User
|
|
client *proton.Client
|
|
reporter reporter.Reporter
|
|
sendHash *sendrecorder.SendRecorder
|
|
|
|
eventCh *async.QueuedChannel[events.Event]
|
|
eventLock safe.RWMutex
|
|
|
|
tasks *async.Group
|
|
|
|
maxSyncMemory uint64
|
|
|
|
panicHandler async.PanicHandler
|
|
configStatus *configstatus.ConfigurationStatus
|
|
telemetryManager telemetry.Availability
|
|
// goStatusProgress triggers a check/sending if progress is needed.
|
|
goStatusProgress func()
|
|
|
|
eventService *userevents.Service
|
|
identityService *useridentity.Service
|
|
smtpService *smtp.Service
|
|
imapService *imapservice.Service
|
|
telemetryService *telemetryservice.Service
|
|
|
|
serviceGroup *orderedtasks.OrderedCancelGroup
|
|
}
|
|
|
|
func New(
|
|
ctx context.Context,
|
|
encVault *vault.User,
|
|
client *proton.Client,
|
|
reporter reporter.Reporter,
|
|
apiUser proton.User,
|
|
crashHandler async.PanicHandler,
|
|
showAllMail bool,
|
|
maxSyncMemory uint64,
|
|
statsDir string,
|
|
telemetryManager telemetry.Availability,
|
|
imapServerManager imapservice.IMAPServerManager,
|
|
smtpServerManager smtp.ServerManager,
|
|
eventSubscription events.Subscription,
|
|
syncService syncservice.Regulator,
|
|
syncConfigDir string,
|
|
) (*User, error) {
|
|
user, err := newImpl(
|
|
ctx,
|
|
encVault,
|
|
client,
|
|
reporter,
|
|
apiUser,
|
|
crashHandler,
|
|
showAllMail,
|
|
maxSyncMemory,
|
|
statsDir,
|
|
telemetryManager,
|
|
imapServerManager,
|
|
smtpServerManager,
|
|
eventSubscription,
|
|
syncService,
|
|
syncConfigDir,
|
|
)
|
|
if err != nil {
|
|
// Cleanup any pending resources on error
|
|
if user != nil {
|
|
user.Close()
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
// New returns a new user.
|
|
func newImpl(
|
|
ctx context.Context,
|
|
encVault *vault.User,
|
|
client *proton.Client,
|
|
reporter reporter.Reporter,
|
|
apiUser proton.User,
|
|
crashHandler async.PanicHandler,
|
|
showAllMail bool,
|
|
maxSyncMemory uint64,
|
|
statsDir string,
|
|
telemetryManager telemetry.Availability,
|
|
imapServerManager imapservice.IMAPServerManager,
|
|
smtpServerManager smtp.ServerManager,
|
|
eventSubscription events.Subscription,
|
|
syncService syncservice.Regulator,
|
|
syncConfigDir string,
|
|
) (*User, error) {
|
|
logrus.WithField("userID", apiUser.ID).Info("Creating new user")
|
|
|
|
// Migrate Sync Status from Vault.
|
|
if err := migrateSyncStatusFromVault(encVault, syncConfigDir, apiUser.ID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Get the user's API addresses.
|
|
apiAddrs, err := client.GetAddresses(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get addresses: %w", err)
|
|
}
|
|
|
|
// Get the user's API labels.
|
|
apiLabels, err := client.GetLabels(ctx, proton.LabelTypeSystem, proton.LabelTypeFolder, proton.LabelTypeLabel)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get labels: %w", err)
|
|
}
|
|
|
|
identityState := useridentity.NewState(apiUser, apiAddrs, client)
|
|
|
|
logrus.WithFields(logrus.Fields{
|
|
"userID": apiUser.ID,
|
|
"numAddr": len(apiAddrs),
|
|
"numLabels": len(apiLabels),
|
|
}).Info("Creating user object")
|
|
|
|
configStatusFile := filepath.Join(statsDir, apiUser.ID+".json")
|
|
configStatus, err := configstatus.LoadConfigurationStatus(configStatusFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to init configuration status file: %w", err)
|
|
}
|
|
|
|
sendRecorder := sendrecorder.NewSendRecorder(sendrecorder.SendEntryExpiry)
|
|
|
|
// Create the user object.
|
|
user := &User{
|
|
log: logrus.WithField("userID", apiUser.ID),
|
|
id: apiUser.ID,
|
|
|
|
vault: encVault,
|
|
client: client,
|
|
reporter: reporter,
|
|
sendHash: sendRecorder,
|
|
|
|
eventCh: async.NewQueuedChannel[events.Event](0, 0, crashHandler, fmt.Sprintf("bridge-user-%v", apiUser.ID)),
|
|
eventLock: safe.NewRWMutex(),
|
|
|
|
tasks: async.NewGroup(context.Background(), crashHandler),
|
|
|
|
maxSyncMemory: maxSyncMemory,
|
|
|
|
panicHandler: crashHandler,
|
|
|
|
configStatus: configStatus,
|
|
telemetryManager: telemetryManager,
|
|
|
|
serviceGroup: orderedtasks.NewOrderedCancelGroup(crashHandler),
|
|
smtpService: nil,
|
|
}
|
|
|
|
user.eventService = userevents.NewService(
|
|
apiUser.ID,
|
|
client,
|
|
userevents.NewVaultEventIDStore(encVault),
|
|
user,
|
|
EventPeriod,
|
|
EventJitter,
|
|
5*time.Minute,
|
|
crashHandler,
|
|
)
|
|
|
|
addressMode := usertypes.VaultToAddressMode(encVault.AddressMode())
|
|
|
|
user.identityService = useridentity.NewService(user.eventService, user, identityState, encVault, user)
|
|
|
|
user.telemetryService = telemetryservice.NewService(apiUser.ID, client, user.eventService)
|
|
|
|
user.smtpService = smtp.NewService(
|
|
apiUser.ID,
|
|
client,
|
|
sendRecorder,
|
|
crashHandler,
|
|
reporter,
|
|
encVault,
|
|
encVault,
|
|
user,
|
|
user.eventService,
|
|
addressMode,
|
|
identityState.Clone(),
|
|
smtpServerManager,
|
|
)
|
|
|
|
user.imapService = imapservice.NewService(
|
|
client,
|
|
identityState.Clone(),
|
|
user,
|
|
user.eventService,
|
|
imapServerManager,
|
|
user,
|
|
encVault,
|
|
encVault,
|
|
crashHandler,
|
|
sendRecorder,
|
|
user,
|
|
reporter,
|
|
addressMode,
|
|
eventSubscription,
|
|
syncConfigDir,
|
|
user.maxSyncMemory,
|
|
showAllMail,
|
|
)
|
|
|
|
// Check for status_progress when triggered.
|
|
user.goStatusProgress = user.tasks.PeriodicOrTrigger(configstatus.ProgressCheckInterval, 0, func(ctx context.Context) {
|
|
user.SendConfigStatusProgress(ctx)
|
|
})
|
|
defer user.goStatusProgress()
|
|
|
|
// When we receive an auth object, we update it in the vault.
|
|
// This will be used to authorize the user on the next run.
|
|
user.client.AddAuthHandler(func(auth proton.Auth) {
|
|
if err := user.vault.SetAuth(auth.UID, auth.RefreshToken); err != nil {
|
|
user.log.WithError(err).Error("Failed to update auth in vault")
|
|
}
|
|
})
|
|
|
|
// When we are deauthorized, we send a deauth event to the event channel.
|
|
// Bridge will react to this event by logging out the user.
|
|
user.client.AddDeauthHandler(func() {
|
|
user.eventCh.Enqueue(events.UserDeauth{
|
|
UserID: user.ID(),
|
|
})
|
|
})
|
|
|
|
// Log all requests made by the user.
|
|
user.client.AddPostRequestHook(func(_ *resty.Client, r *resty.Response) error {
|
|
user.log.Infof("%v: %v %v", r.Status(), r.Request.Method, r.Request.URL)
|
|
return nil
|
|
})
|
|
|
|
// Start Event Service
|
|
lastEventID, err := user.eventService.Start(ctx, user.serviceGroup)
|
|
if err != nil {
|
|
return user, fmt.Errorf("failed to start event service: %w", err)
|
|
}
|
|
|
|
// Start Telemetry Service
|
|
user.telemetryService.Start(ctx, user.serviceGroup)
|
|
|
|
// Start Identity Service
|
|
user.identityService.Start(ctx, user.serviceGroup)
|
|
|
|
// Start SMTP Service
|
|
if err := user.smtpService.Start(ctx, user.serviceGroup); err != nil {
|
|
return user, fmt.Errorf("failed to start smtp service: %w", err)
|
|
}
|
|
|
|
// Start IMAP Service
|
|
if err := user.imapService.Start(ctx, user.serviceGroup, syncService, lastEventID); err != nil {
|
|
return user, fmt.Errorf("failed to start imap service: %w", err)
|
|
}
|
|
|
|
user.eventService.Resume()
|
|
|
|
return user, nil
|
|
}
|
|
|
|
// ID returns the user's ID.
|
|
func (user *User) ID() string {
|
|
return user.id
|
|
}
|
|
|
|
// Name returns the user's username.
|
|
func (user *User) Name() string {
|
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute))
|
|
defer cancel()
|
|
|
|
apiUser, err := user.identityService.GetAPIUser(ctx)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
return apiUser.Name
|
|
}
|
|
|
|
// Match matches the given query against the user's username and email addresses.
|
|
func (user *User) Match(query string) bool {
|
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute))
|
|
defer cancel()
|
|
|
|
apiUser, err := user.identityService.GetAPIUser(ctx)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if query == apiUser.Name {
|
|
return true
|
|
}
|
|
|
|
apiAddrs, err := user.identityService.GetAddresses(ctx)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
for _, addr := range apiAddrs {
|
|
if query == addr.Email {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Emails returns all the user's active email addresses.
|
|
// It returns them in sorted order; the user's primary address is first.
|
|
func (user *User) Emails() []string {
|
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute))
|
|
defer cancel()
|
|
|
|
apiAddrs, err := user.identityService.GetAddresses(ctx)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
addresses := xslices.Filter(maps.Values(apiAddrs), func(addr proton.Address) bool {
|
|
return addr.Status == proton.AddressStatusEnabled && addr.Type != proton.AddressTypeExternal
|
|
})
|
|
|
|
slices.SortFunc(addresses, func(a, b proton.Address) bool {
|
|
return a.Order < b.Order
|
|
})
|
|
|
|
return xslices.Map(addresses, func(addr proton.Address) string {
|
|
return addr.Email
|
|
})
|
|
}
|
|
|
|
// GetAddressMode returns the user's current address mode.
|
|
func (user *User) GetAddressMode() vault.AddressMode {
|
|
return user.vault.AddressMode()
|
|
}
|
|
|
|
// SetAddressMode sets the user's address mode.
|
|
func (user *User) SetAddressMode(ctx context.Context, mode vault.AddressMode) error {
|
|
user.log.WithField("mode", mode).Info("Setting address mode")
|
|
|
|
if err := user.vault.SetAddressMode(mode); err != nil {
|
|
return fmt.Errorf("failed to set address mode: %w", err)
|
|
}
|
|
|
|
if err := user.smtpService.SetAddressMode(ctx, usertypes.VaultToAddressMode(mode)); err != nil {
|
|
return fmt.Errorf("failed to set smtp address mode: %w", err)
|
|
}
|
|
|
|
if err := user.imapService.SetAddressMode(ctx, usertypes.VaultToAddressMode(mode)); err != nil {
|
|
return fmt.Errorf("failed to imap address mode: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// BadEventFeedbackResync sends user feedback whether should do message re-sync.
|
|
func (user *User) BadEventFeedbackResync(ctx context.Context) error {
|
|
if err := user.imapService.OnBadEventResync(ctx); err != nil {
|
|
return fmt.Errorf("failed to execute bad event request on imap service: %w", err)
|
|
}
|
|
|
|
if err := user.identityService.Resync(ctx); err != nil {
|
|
return fmt.Errorf("failed to resync identity service: %w", err)
|
|
}
|
|
|
|
if err := user.smtpService.Resync(ctx); err != nil {
|
|
return fmt.Errorf("failed to resync smtp service: %w", err)
|
|
}
|
|
|
|
if err := user.imapService.Resync(ctx); err != nil {
|
|
return fmt.Errorf("failed to resync imap service: %w", err)
|
|
}
|
|
|
|
user.eventService.Resume()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (user *User) OnBadEvent(ctx context.Context) {
|
|
if err := user.imapService.OnBadEvent(ctx); err != nil {
|
|
user.log.WithError(err).Error("Failed to notify imap service of bad event")
|
|
}
|
|
}
|
|
|
|
// SetShowAllMail sets whether to show the All Mail mailbox.
|
|
func (user *User) SetShowAllMail(show bool) {
|
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute))
|
|
defer cancel()
|
|
|
|
user.log.WithField("show", show).Info("Setting show all mail")
|
|
|
|
if err := user.imapService.ShowAllMail(ctx, show); err != nil {
|
|
user.log.WithError(err).Error("Failed to set show all mail")
|
|
}
|
|
}
|
|
|
|
// GetGluonIDs returns the users gluon IDs.
|
|
func (user *User) GetGluonIDs() map[string]string {
|
|
return user.vault.GetGluonIDs()
|
|
}
|
|
|
|
// GetGluonID returns the gluon ID for the given address, if present.
|
|
func (user *User) GetGluonID(addrID string) (string, bool) {
|
|
if gluonID, ok := user.vault.GetGluonIDs()[addrID]; ok {
|
|
return gluonID, true
|
|
}
|
|
|
|
if user.vault.AddressMode() != vault.CombinedMode {
|
|
return "", false
|
|
}
|
|
|
|
// If there is only one address, return its gluon ID.
|
|
// This can happen if we are in combined mode and the primary address ID has changed.
|
|
if gluonIDs := maps.Values(user.vault.GetGluonIDs()); len(gluonIDs) == 1 {
|
|
if err := user.vault.SetGluonID(addrID, gluonIDs[0]); err != nil {
|
|
user.log.WithError(err).Error("Failed to set gluon ID for updated primary address")
|
|
}
|
|
|
|
return gluonIDs[0], true
|
|
}
|
|
|
|
return "", false
|
|
}
|
|
|
|
// SetGluonID sets the gluon ID for the given address.
|
|
func (user *User) SetGluonID(addrID, gluonID string) error {
|
|
user.log.WithFields(logrus.Fields{
|
|
"addrID": addrID,
|
|
"gluonID": gluonID,
|
|
}).Info("Setting gluon ID")
|
|
|
|
return user.vault.SetGluonID(addrID, gluonID)
|
|
}
|
|
|
|
// RemoveGluonID removes the gluon ID for the given address.
|
|
func (user *User) RemoveGluonID(addrID, gluonID string) error {
|
|
user.log.WithFields(logrus.Fields{
|
|
"addrID": addrID,
|
|
"gluonID": gluonID,
|
|
}).Info("Removing gluon ID")
|
|
|
|
return user.vault.RemoveGluonID(addrID, gluonID)
|
|
}
|
|
|
|
// GluonKey returns the user's gluon key from the vault.
|
|
func (user *User) GluonKey() []byte {
|
|
return user.vault.GluonKey()
|
|
}
|
|
|
|
// BridgePass returns the user's bridge password, used for authentication over SMTP and IMAP.
|
|
func (user *User) BridgePass() []byte {
|
|
return algo.B64RawEncode(user.vault.BridgePass())
|
|
}
|
|
|
|
// UsedSpace returns the total space used by the user on the API.
|
|
func (user *User) UsedSpace() int {
|
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute))
|
|
defer cancel()
|
|
|
|
apiUser, err := user.identityService.GetAPIUser(ctx)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
return apiUser.UsedSpace
|
|
}
|
|
|
|
// MaxSpace returns the amount of space the user can use on the API.
|
|
func (user *User) MaxSpace() int {
|
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute))
|
|
defer cancel()
|
|
|
|
apiUser, err := user.identityService.GetAPIUser(ctx)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
return apiUser.MaxSpace
|
|
}
|
|
|
|
// GetEventCh returns a channel which notifies of events happening to the user (such as deauth, address change).
|
|
func (user *User) GetEventCh() <-chan events.Event {
|
|
return user.eventCh.GetChannel()
|
|
}
|
|
|
|
// CheckAuth returns whether the given email and password can be used to authenticate over IMAP or SMTP with this user.
|
|
// It returns the address ID of the authenticated address.
|
|
func (user *User) CheckAuth(email string, password []byte) (string, error) {
|
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute))
|
|
defer cancel()
|
|
|
|
return user.identityService.CheckAuth(ctx, email, password)
|
|
}
|
|
|
|
// OnStatusUp is called when the connection goes up.
|
|
func (user *User) OnStatusUp(ctx context.Context) {
|
|
user.log.Info("Connection is up")
|
|
|
|
user.eventService.Resume()
|
|
|
|
if err := user.imapService.ResumeSync(ctx); err != nil {
|
|
user.log.WithError(err).Error("Failed to resume sync")
|
|
}
|
|
}
|
|
|
|
// OnStatusDown is called when the connection goes down.
|
|
func (user *User) OnStatusDown(ctx context.Context) {
|
|
user.log.Info("Connection is down")
|
|
|
|
user.eventService.Pause()
|
|
if err := user.imapService.CancelSync(ctx); err != nil {
|
|
user.log.WithError(err).Error("Failed to cancel sync")
|
|
}
|
|
}
|
|
|
|
// Logout logs the user out from the API.
|
|
func (user *User) Logout(ctx context.Context, withAPI bool) error {
|
|
user.log.WithField("withAPI", withAPI).Info("Logging out user")
|
|
|
|
user.log.Debug("Canceling ongoing tasks")
|
|
|
|
if err := user.smtpService.OnLogout(ctx); err != nil {
|
|
return fmt.Errorf("failed to remove user from smtp server: %w", err)
|
|
}
|
|
|
|
if err := user.imapService.OnLogout(ctx); err != nil {
|
|
return fmt.Errorf("failed to remove user from imap server: %w", err)
|
|
}
|
|
|
|
// Stop Services
|
|
user.serviceGroup.CancelAndWait()
|
|
|
|
// Cleanup Event Service.
|
|
user.eventService.Close()
|
|
|
|
// Close imap service.
|
|
user.imapService.Close()
|
|
|
|
user.tasks.CancelAndWait()
|
|
|
|
if withAPI {
|
|
user.log.Debug("Logging out from API")
|
|
|
|
if err := user.client.AuthDelete(ctx); err != nil {
|
|
user.log.WithError(err).Warn("Failed to delete auth")
|
|
}
|
|
}
|
|
|
|
user.log.Debug("Clearing vault secrets")
|
|
|
|
if err := user.vault.Clear(); err != nil {
|
|
return fmt.Errorf("failed to clear vault: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close closes ongoing connections and cleans up resources.
|
|
func (user *User) Close() {
|
|
user.log.Info("Closing user")
|
|
|
|
// Stop Services
|
|
user.serviceGroup.CancelAndWait()
|
|
|
|
// Cleanup Event Service.
|
|
user.eventService.Close()
|
|
|
|
// Close imap service.
|
|
user.imapService.Close()
|
|
|
|
// Stop any ongoing background tasks.
|
|
user.tasks.CancelAndWait()
|
|
|
|
// Close the user's API client.
|
|
user.client.Close()
|
|
|
|
// Close the user's notify channel.
|
|
user.eventCh.CloseAndDiscardQueued()
|
|
|
|
// Close the user's vault.
|
|
if err := user.vault.Close(); err != nil {
|
|
user.log.WithError(err).Error("Failed to close vault")
|
|
}
|
|
}
|
|
|
|
// IsTelemetryEnabled check if the telemetry is enabled or disabled for this user.
|
|
func (user *User) IsTelemetryEnabled(ctx context.Context) bool {
|
|
return user.telemetryService.IsTelemetryEnabled(ctx)
|
|
}
|
|
|
|
// SendTelemetry send telemetry request.
|
|
func (user *User) SendTelemetry(ctx context.Context, data []byte) error {
|
|
var req proton.SendStatsReq
|
|
if err := json.Unmarshal(data, &req); err != nil {
|
|
user.log.WithError(err).Error("Failed to build telemetry request.")
|
|
if err := user.reporter.ReportMessageWithContext("Failed to build telemetry request.", reporter.Context{
|
|
"error": err,
|
|
}); err != nil {
|
|
logrus.WithError(err).Error("Failed to report telemetry request build error")
|
|
}
|
|
return err
|
|
}
|
|
err := user.client.SendDataEvent(ctx, req)
|
|
if err != nil {
|
|
user.log.WithError(err).Error("Failed to send telemetry.")
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (user *User) ReportSMTPAuthFailed(username string) {
|
|
emails := user.Emails()
|
|
for _, mail := range emails {
|
|
if mail == username {
|
|
user.ReportConfigStatusFailure("invalid username or password")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (user *User) ReportSMTPAuthSuccess(ctx context.Context) {
|
|
user.SendConfigStatusSuccess(ctx)
|
|
}
|
|
|
|
func (user *User) GetSMTPService() *smtp.Service {
|
|
return user.smtpService
|
|
}
|
|
|
|
func (user *User) PublishEvent(_ context.Context, event events.Event) {
|
|
user.eventCh.Enqueue(event)
|
|
}
|
|
|
|
func (user *User) PauseEventLoop() {
|
|
user.eventService.Pause()
|
|
}
|
|
|
|
func (user *User) PauseEventLoopWithWaiter() *userevents.EventPollWaiter {
|
|
return user.eventService.PauseWithWaiter()
|
|
}
|
|
|
|
func (user *User) ResumeEventLoop() {
|
|
user.eventService.Resume()
|
|
}
|