forked from Silverfish/proton-bridge
feat: simple client manager
This commit is contained in:
@ -43,14 +43,14 @@ var (
|
||||
|
||||
// Bridge is a struct handling users.
|
||||
type Bridge struct {
|
||||
config Configer
|
||||
pref PreferenceProvider
|
||||
panicHandler PanicHandler
|
||||
events listener.Listener
|
||||
version string
|
||||
pmapiClientFactory PMAPIProviderFactory
|
||||
credStorer CredentialsStorer
|
||||
storeCache *store.Cache
|
||||
config Configer
|
||||
pref PreferenceProvider
|
||||
panicHandler PanicHandler
|
||||
events listener.Listener
|
||||
version string
|
||||
clientMan *pmapi.ClientManager
|
||||
credStorer CredentialsStorer
|
||||
storeCache *store.Cache
|
||||
|
||||
// users is a list of accounts that have been added to bridge.
|
||||
// They are stored sorted in the credentials store in the order
|
||||
@ -76,22 +76,22 @@ func New(
|
||||
panicHandler PanicHandler,
|
||||
eventListener listener.Listener,
|
||||
version string,
|
||||
pmapiClientFactory PMAPIProviderFactory,
|
||||
clientMan *pmapi.ClientManager,
|
||||
credStorer CredentialsStorer,
|
||||
) *Bridge {
|
||||
log.Trace("Creating new bridge")
|
||||
|
||||
b := &Bridge{
|
||||
config: config,
|
||||
pref: pref,
|
||||
panicHandler: panicHandler,
|
||||
events: eventListener,
|
||||
version: version,
|
||||
pmapiClientFactory: pmapiClientFactory,
|
||||
credStorer: credStorer,
|
||||
storeCache: store.NewCache(config.GetIMAPCachePath()),
|
||||
idleUpdates: make(chan interface{}),
|
||||
lock: sync.RWMutex{},
|
||||
config: config,
|
||||
pref: pref,
|
||||
panicHandler: panicHandler,
|
||||
events: eventListener,
|
||||
version: version,
|
||||
clientMan: clientMan,
|
||||
credStorer: credStorer,
|
||||
storeCache: store.NewCache(config.GetIMAPCachePath()),
|
||||
idleUpdates: make(chan interface{}),
|
||||
lock: sync.RWMutex{},
|
||||
}
|
||||
|
||||
// Allow DoH before starting bridge if the user has previously set this setting.
|
||||
@ -148,9 +148,7 @@ func (b *Bridge) loadUsersFromCredentialsStore() (err error) {
|
||||
for _, userID := range userIDs {
|
||||
l := log.WithField("user", userID)
|
||||
|
||||
apiClient := b.pmapiClientFactory(userID)
|
||||
|
||||
user, newUserErr := newUser(b.panicHandler, userID, b.events, b.credStorer, apiClient, b.storeCache, b.config.GetDBDir())
|
||||
user, newUserErr := newUser(b.panicHandler, userID, b.events, b.credStorer, b.clientMan, b.storeCache, b.config.GetDBDir())
|
||||
if newUserErr != nil {
|
||||
l.WithField("user", userID).WithError(newUserErr).Warn("Could not load user, skipping")
|
||||
continue
|
||||
@ -158,7 +156,7 @@ func (b *Bridge) loadUsersFromCredentialsStore() (err error) {
|
||||
|
||||
b.users = append(b.users, user)
|
||||
|
||||
if initUserErr := user.init(b.idleUpdates, apiClient); initUserErr != nil {
|
||||
if initUserErr := user.init(b.idleUpdates); initUserErr != nil {
|
||||
l.WithField("user", userID).WithError(initUserErr).Warn("Could not initialise user")
|
||||
}
|
||||
}
|
||||
@ -199,7 +197,7 @@ func (b *Bridge) Login(username, password string) (loginClient PMAPIProvider, au
|
||||
|
||||
// We need to use "login" client because we need userID to properly
|
||||
// assign access tokens into token manager.
|
||||
loginClient = b.pmapiClientFactory("login")
|
||||
loginClient = b.clientMan.GetClient("login")
|
||||
|
||||
authInfo, err := loginClient.AuthInfo(username)
|
||||
if err != nil {
|
||||
@ -268,7 +266,7 @@ func (b *Bridge) FinishLogin(loginClient PMAPIProvider, auth *pmapi.Auth, mbPass
|
||||
}
|
||||
|
||||
apiToken := auth.UID() + ":" + auth.RefreshToken
|
||||
apiClient := b.pmapiClientFactory(apiUser.ID)
|
||||
apiClient := b.clientMan.GetClient(apiUser.ID)
|
||||
auth, err = apiClient.AuthRefresh(apiToken)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could refresh token in new client")
|
||||
@ -297,7 +295,7 @@ func (b *Bridge) FinishLogin(loginClient PMAPIProvider, auth *pmapi.Auth, mbPass
|
||||
|
||||
// If it's a new user, generate the user object.
|
||||
if !hasUser {
|
||||
user, err = newUser(b.panicHandler, apiUser.ID, b.events, b.credStorer, apiClient, b.storeCache, b.config.GetDBDir())
|
||||
user, err = newUser(b.panicHandler, apiUser.ID, b.events, b.credStorer, b.clientMan, b.storeCache, b.config.GetDBDir())
|
||||
if err != nil {
|
||||
log.WithField("user", apiUser.ID).WithError(err).Error("Could not create user")
|
||||
return
|
||||
@ -305,7 +303,7 @@ func (b *Bridge) FinishLogin(loginClient PMAPIProvider, auth *pmapi.Auth, mbPass
|
||||
}
|
||||
|
||||
// Set up the user auth and store (which we do for both new and existing users).
|
||||
if err = user.init(b.idleUpdates, apiClient); err != nil {
|
||||
if err = user.init(b.idleUpdates); err != nil {
|
||||
log.WithField("user", user.userID).WithError(err).Error("Could not initialise user")
|
||||
return
|
||||
}
|
||||
@ -407,9 +405,11 @@ func (b *Bridge) DeleteUser(userID string, clearStore bool) error {
|
||||
|
||||
// ReportBug reports a new bug from the user.
|
||||
func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address, emailClient string) error {
|
||||
apiClient := b.pmapiClientFactory("bug_reporter")
|
||||
c := b.clientMan.GetClient("bug_reporter")
|
||||
defer func() { _ = c.Logout() }()
|
||||
|
||||
title := "[Bridge] Bug"
|
||||
err := apiClient.ReportBugWithEmailClient(
|
||||
if err := c.ReportBugWithEmailClient(
|
||||
osType,
|
||||
osVersion,
|
||||
title,
|
||||
@ -417,23 +417,26 @@ func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address,
|
||||
accountName,
|
||||
address,
|
||||
emailClient,
|
||||
)
|
||||
if err != nil {
|
||||
); err != nil {
|
||||
log.Error("Reporting bug failed: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Bug successfully reported")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendMetric sends a metric. We don't want to return any errors, only log them.
|
||||
func (b *Bridge) SendMetric(m m.Metric) {
|
||||
apiClient := b.pmapiClientFactory("metric_reporter")
|
||||
c := b.clientMan.GetClient("metric_reporter")
|
||||
defer func() { _ = c.Logout() }()
|
||||
|
||||
cat, act, lab := m.Get()
|
||||
err := apiClient.SendSimpleMetric(string(cat), string(act), string(lab))
|
||||
if err != nil {
|
||||
if err := c.SendSimpleMetric(string(cat), string(act), string(lab)); err != nil {
|
||||
log.Error("Sending metric failed: ", err)
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"cat": cat,
|
||||
"act": act,
|
||||
|
||||
@ -43,7 +43,8 @@ type PanicHandler interface {
|
||||
HandlePanic()
|
||||
}
|
||||
|
||||
type PMAPIProviderFactory func(string) PMAPIProvider
|
||||
type Clientman interface {
|
||||
}
|
||||
|
||||
type PMAPIProvider interface {
|
||||
SetAuths(auths chan<- *pmapi.Auth)
|
||||
|
||||
@ -41,7 +41,7 @@ type User struct {
|
||||
log *logrus.Entry
|
||||
panicHandler PanicHandler
|
||||
listener listener.Listener
|
||||
apiClient PMAPIProvider
|
||||
clientMan *pmapi.ClientManager
|
||||
credStorer CredentialsStorer
|
||||
|
||||
imapUpdatesChannel chan interface{}
|
||||
@ -67,7 +67,7 @@ func newUser(
|
||||
userID string,
|
||||
eventListener listener.Listener,
|
||||
credStorer CredentialsStorer,
|
||||
apiClient PMAPIProvider,
|
||||
clientMan *pmapi.ClientManager,
|
||||
storeCache *store.Cache,
|
||||
storeDir string,
|
||||
) (u *User, err error) {
|
||||
@ -84,7 +84,7 @@ func newUser(
|
||||
panicHandler: panicHandler,
|
||||
listener: eventListener,
|
||||
credStorer: credStorer,
|
||||
apiClient: apiClient,
|
||||
clientMan: clientMan,
|
||||
storeCache: storeCache,
|
||||
storePath: getUserStorePath(storeDir, userID),
|
||||
userID: userID,
|
||||
@ -94,16 +94,16 @@ func newUser(
|
||||
return
|
||||
}
|
||||
|
||||
func (u *User) client() PMAPIProvider {
|
||||
return u.clientMan.GetClient(u.userID)
|
||||
}
|
||||
|
||||
// init initialises a bridge user. This includes reloading its credentials from the credentials store
|
||||
// (such as when logging out and back in, you need to reload the credentials because the new credentials will
|
||||
// have the apitoken and password), authorising the user against the api, loading the user store (creating a new one
|
||||
// if necessary), and setting the imap idle updates channel (used to send imap idle updates to the imap backend if
|
||||
// something in the store changed).
|
||||
func (u *User) init(idleUpdates chan interface{}, apiClient PMAPIProvider) (err error) {
|
||||
// If this is an existing user, we still need a new api client to get a new refresh token.
|
||||
// If it's a new user, doesn't matter really; this is basically a noop in this case.
|
||||
u.apiClient = apiClient
|
||||
|
||||
func (u *User) init(idleUpdates chan interface{}) (err error) {
|
||||
u.unlockingKeyringLock.Lock()
|
||||
u.wasKeyringUnlocked = false
|
||||
u.unlockingKeyringLock.Unlock()
|
||||
@ -118,7 +118,7 @@ func (u *User) init(idleUpdates chan interface{}, apiClient PMAPIProvider) (err
|
||||
|
||||
// Set up the auth channel on which auths from the api client are sent.
|
||||
u.authChannel = make(chan *pmapi.Auth)
|
||||
u.apiClient.SetAuths(u.authChannel)
|
||||
u.client().SetAuths(u.authChannel)
|
||||
u.hasAPIAuth = false
|
||||
go func() {
|
||||
defer u.panicHandler.HandlePanic()
|
||||
@ -147,7 +147,7 @@ func (u *User) init(idleUpdates chan interface{}, apiClient PMAPIProvider) (err
|
||||
}
|
||||
u.store = nil
|
||||
}
|
||||
store, err := store.New(u.panicHandler, u, u.apiClient, u.listener, u.storePath, u.storeCache)
|
||||
store, err := store.New(u.panicHandler, u, u.client(), u.listener, u.storePath, u.storeCache)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create store")
|
||||
}
|
||||
@ -216,11 +216,11 @@ func (u *User) unlockIfNecessary() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := u.apiClient.Unlock(u.creds.MailboxPassword); err != nil {
|
||||
if _, err := u.client().Unlock(u.creds.MailboxPassword); err != nil {
|
||||
return errors.Wrap(err, "failed to unlock user")
|
||||
}
|
||||
|
||||
if err := u.apiClient.UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
|
||||
if err := u.client().UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
|
||||
return errors.Wrap(err, "failed to unlock user addresses")
|
||||
}
|
||||
|
||||
@ -236,17 +236,17 @@ func (u *User) authorizeAndUnlock() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
auth, err := u.apiClient.AuthRefresh(u.creds.APIToken)
|
||||
auth, err := u.client().AuthRefresh(u.creds.APIToken)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to refresh API auth")
|
||||
}
|
||||
u.authChannel <- auth
|
||||
|
||||
if _, err = u.apiClient.Unlock(u.creds.MailboxPassword); err != nil {
|
||||
if _, err = u.client().Unlock(u.creds.MailboxPassword); err != nil {
|
||||
return errors.Wrap(err, "failed to unlock user")
|
||||
}
|
||||
|
||||
if err = u.apiClient.UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
|
||||
if err = u.client().UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
|
||||
return errors.Wrap(err, "failed to unlock user addresses")
|
||||
}
|
||||
|
||||
@ -321,7 +321,7 @@ func getUserStorePath(storeDir string, userID string) (path string) {
|
||||
// Do not use! It's only for backward compatibility of old SMTP and IMAP implementations.
|
||||
// After proper refactor of SMTP and IMAP remove this method.
|
||||
func (u *User) GetTemporaryPMAPIClient() PMAPIProvider {
|
||||
return u.apiClient
|
||||
return u.client()
|
||||
}
|
||||
|
||||
// ID returns the user's userID.
|
||||
@ -462,20 +462,20 @@ func (u *User) UpdateUser() error {
|
||||
return errors.Wrap(err, "cannot update user")
|
||||
}
|
||||
|
||||
_, err := u.apiClient.UpdateUser()
|
||||
_, err := u.client().UpdateUser()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = u.apiClient.Unlock(u.creds.MailboxPassword); err != nil {
|
||||
if _, err = u.client().Unlock(u.creds.MailboxPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := u.apiClient.UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
|
||||
if err := u.client().UnlockAddresses([]byte(u.creds.MailboxPassword)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
emails := u.apiClient.Addresses().ActiveEmails()
|
||||
emails := u.client().Addresses().ActiveEmails()
|
||||
if err := u.credStorer.UpdateEmails(u.userID, emails); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -548,10 +548,10 @@ func (u *User) Logout() (err error) {
|
||||
u.wasKeyringUnlocked = false
|
||||
u.unlockingKeyringLock.Unlock()
|
||||
|
||||
if err = u.apiClient.Logout(); err != nil {
|
||||
if err = u.client().Logout(); err != nil {
|
||||
u.log.WithError(err).Warn("Could not log user out from API client")
|
||||
}
|
||||
u.apiClient.SetAuths(nil)
|
||||
u.client().SetAuths(nil)
|
||||
|
||||
// Logout needs to stop auth channel so when user logs back in
|
||||
// it can register again with new client.
|
||||
|
||||
@ -26,8 +26,6 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func New(cfg bridge.Configer, _ listener.Listener) bridge.PMAPIProviderFactory {
|
||||
return func(userID string) bridge.PMAPIProvider {
|
||||
return pmapi.NewClient(cfg.GetAPIConfig(), userID)
|
||||
}
|
||||
func GetClientConfig(config bridge.Configer, _ listener.Listener) *pmapi.ClientConfig {
|
||||
return config.GetAPIConfig()
|
||||
}
|
||||
|
||||
@ -29,10 +29,10 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
func New(config bridge.Configer, listener listener.Listener) bridge.PMAPIProviderFactory {
|
||||
cfg := config.GetAPIConfig()
|
||||
func GetClientConfig(config bridge.Configer, listener listener.Listener) *pmapi.ClientConfig {
|
||||
clientConfig := config.GetAPIConfig()
|
||||
|
||||
pin := pmapi.NewPMAPIPinning(cfg.AppVersion)
|
||||
pin := pmapi.NewPMAPIPinning(clientConfig.AppVersion)
|
||||
pin.ReportCertIssueLocal = func() {
|
||||
listener.Emit(events.TLSCertIssue, "")
|
||||
}
|
||||
@ -41,14 +41,12 @@ func New(config bridge.Configer, listener listener.Listener) bridge.PMAPIProvide
|
||||
// - IdleConnTimeout: 5 * time.Minute,
|
||||
// - ExpectContinueTimeout: 500 * time.Millisecond,
|
||||
// - ResponseHeaderTimeout: 30 * time.Second,
|
||||
cfg.Transport = pin.TransportWithPinning()
|
||||
clientConfig.Transport = pin.TransportWithPinning()
|
||||
|
||||
// We set additional timeouts/thresholds for the request as a whole:
|
||||
cfg.Timeout = 10 * time.Minute // Overall request timeout (~25MB / 10 mins => ~40kB/s, should be reasonable).
|
||||
cfg.FirstReadTimeout = 30 * time.Second // 30s to match 30s response header timeout.
|
||||
cfg.MinSpeed = 1 << 13 // Enforce minimum download speed of 8kB/s.
|
||||
clientConfig.Timeout = 10 * time.Minute // Overall request timeout (~25MB / 10 mins => ~40kB/s, should be reasonable).
|
||||
clientConfig.FirstReadTimeout = 30 * time.Second // 30s to match 30s response header timeout.
|
||||
clientConfig.MinSpeed = 1 << 13 // Enforce minimum download speed of 8kB/s.
|
||||
|
||||
return func(userID string) bridge.PMAPIProvider {
|
||||
return pmapi.NewClient(cfg, userID)
|
||||
}
|
||||
return clientConfig
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user