diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index 3c551e6d..7c1dcf33 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -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 - clientMan *pmapi.ClientManager - credStorer CredentialsStorer - storeCache *store.Cache + config Configer + pref PreferenceProvider + panicHandler PanicHandler + events listener.Listener + version string + clientManager *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, - clientMan *pmapi.ClientManager, + clientManager *pmapi.ClientManager, credStorer CredentialsStorer, ) *Bridge { log.Trace("Creating new bridge") b := &Bridge{ - 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{}, + config: config, + pref: pref, + panicHandler: panicHandler, + events: eventListener, + version: version, + clientManager: clientManager, + 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. @@ -105,6 +105,11 @@ func New( b.watchBridgeOutdated() }() + go func() { + defer panicHandler.HandlePanic() + b.watchUserAuths() + }() + if b.credStorer == nil { log.Error("Bridge has no credentials store") } else if err := b.loadUsersFromCredentialsStore(); err != nil { @@ -148,7 +153,7 @@ func (b *Bridge) loadUsersFromCredentialsStore() (err error) { for _, userID := range userIDs { l := log.WithField("user", userID) - user, newUserErr := newUser(b.panicHandler, userID, b.events, b.credStorer, b.clientMan, b.storeCache, b.config.GetDBDir()) + user, newUserErr := newUser(b.panicHandler, userID, b.events, b.credStorer, b.clientManager, b.storeCache, b.config.GetDBDir()) if newUserErr != nil { l.WithField("user", userID).WithError(newUserErr).Warn("Could not load user, skipping") continue @@ -173,6 +178,18 @@ func (b *Bridge) watchBridgeOutdated() { } } +func (b *Bridge) watchUserAuths() { + for auth := range b.clientManager.GetBridgeAuthChannel() { + user, ok := b.hasUser(auth.UserID) + + if !ok { + continue + } + + user.ReceiveAPIAuth(auth.Auth) + } +} + func (b *Bridge) closeAllConnections() { for _, user := range b.users { user.closeAllConnections() @@ -195,9 +212,8 @@ func (b *Bridge) Login(username, password string) (loginClient PMAPIProvider, au b.crashBandicoot(username) - // We need to use "login" client because we need userID to properly - // assign access tokens into token manager. - loginClient = b.clientMan.GetClient("login") + // We need to use "login" client because we need userID to properly assign access tokens into token manager. + loginClient = b.clientManager.GetClient("login") authInfo, err := loginClient.AuthInfo(username) if err != nil { @@ -227,29 +243,22 @@ func (b *Bridge) FinishLogin(loginClient PMAPIProvider, auth *pmapi.Auth, mbPass b.lock.Lock() defer b.lock.Unlock() + defer loginClient.Logout() + mbPassword, err = pmapi.HashMailboxPassword(mbPassword, auth.KeySalt) if err != nil { log.WithError(err).Error("Could not hash mailbox password") - if logoutErr := loginClient.Logout(); logoutErr != nil { - log.WithError(logoutErr).Error("Clean login session after hash password failed.") - } return } if _, err = loginClient.Unlock(mbPassword); err != nil { log.WithError(err).Error("Could not decrypt keyring") - if logoutErr := loginClient.Logout(); logoutErr != nil { - log.WithError(logoutErr).Error("Clean login session after unlock failed.") - } return } apiUser, err := loginClient.CurrentUser() if err != nil { log.WithError(err).Error("Could not get login API user") - if logoutErr := loginClient.Logout(); logoutErr != nil { - log.WithError(logoutErr).Error("Clean login session after get current user failed.") - } return } @@ -259,20 +268,13 @@ func (b *Bridge) FinishLogin(loginClient PMAPIProvider, auth *pmapi.Auth, mbPass if hasUser && user.IsConnected() { err = errors.New("user is already logged in") log.WithError(err).Warn("User is already logged in") - if logoutErr := loginClient.Logout(); logoutErr != nil { - log.WithError(logoutErr).Warn("Could not discard auth generated during second login") - } return } - apiToken := auth.UID() + ":" + auth.RefreshToken - apiClient := b.clientMan.GetClient(apiUser.ID) - auth, err = apiClient.AuthRefresh(apiToken) + apiClient := b.clientManager.GetClient(apiUser.ID) + auth, err = apiClient.AuthRefresh(auth.GenToken()) if err != nil { log.WithError(err).Error("Could refresh token in new client") - if logoutErr := loginClient.Logout(); logoutErr != nil { - log.WithError(logoutErr).Warn("Could not discard auth generated after auth refresh") - } return } @@ -280,22 +282,18 @@ func (b *Bridge) FinishLogin(loginClient PMAPIProvider, auth *pmapi.Auth, mbPass apiUser, err = apiClient.CurrentUser() if err != nil { log.WithError(err).Error("Could not get current API user") - if logoutErr := loginClient.Logout(); logoutErr != nil { - log.WithError(logoutErr).Error("Clean login session after get current user failed.") - } return } - apiToken = auth.UID() + ":" + auth.RefreshToken activeEmails := apiClient.Addresses().ActiveEmails() - if _, err = b.credStorer.Add(apiUser.ID, apiUser.Name, apiToken, mbPassword, activeEmails); err != nil { + if _, err = b.credStorer.Add(apiUser.ID, apiUser.Name, auth.GenToken(), mbPassword, activeEmails); err != nil { log.WithError(err).Error("Could not add user to credentials store") return } // If it's a new user, generate the user object. if !hasUser { - user, err = newUser(b.panicHandler, apiUser.ID, b.events, b.credStorer, b.clientMan, b.storeCache, b.config.GetDBDir()) + user, err = newUser(b.panicHandler, apiUser.ID, b.events, b.credStorer, b.clientManager, b.storeCache, b.config.GetDBDir()) if err != nil { log.WithField("user", apiUser.ID).WithError(err).Error("Could not create user") return @@ -405,8 +403,8 @@ 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 { - c := b.clientMan.GetClient("bug_reporter") - defer func() { _ = c.Logout() }() + c := b.clientManager.GetClient("bug_reporter") + defer c.Logout() title := "[Bridge] Bug" if err := c.ReportBugWithEmailClient( @@ -429,8 +427,8 @@ func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address, // SendMetric sends a metric. We don't want to return any errors, only log them. func (b *Bridge) SendMetric(m m.Metric) { - c := b.clientMan.GetClient("metric_reporter") - defer func() { _ = c.Logout() }() + c := b.clientManager.GetClient("metric_reporter") + defer c.Logout() cat, act, lab := m.Get() if err := c.SendSimpleMetric(string(cat), string(act), string(lab)); err != nil { diff --git a/internal/bridge/types.go b/internal/bridge/types.go index 558990aa..e74dd8ec 100644 --- a/internal/bridge/types.go +++ b/internal/bridge/types.go @@ -47,7 +47,6 @@ type Clientman interface { } type PMAPIProvider interface { - SetAuths(auths chan<- *pmapi.Auth) Auth(username, password string, info *pmapi.AuthInfo) (*pmapi.Auth, error) AuthInfo(username string) (*pmapi.AuthInfo, error) AuthRefresh(token string) (*pmapi.Auth, error) @@ -56,7 +55,8 @@ type PMAPIProvider interface { CurrentUser() (*pmapi.User, error) UpdateUser() (*pmapi.User, error) Addresses() pmapi.AddressList - Logout() error + + Logout() GetEvent(eventID string) (*pmapi.Event, error) diff --git a/internal/bridge/user.go b/internal/bridge/user.go index 499d7cd7..1903fbca 100644 --- a/internal/bridge/user.go +++ b/internal/bridge/user.go @@ -53,9 +53,8 @@ type User struct { userID string creds *credentials.Credentials - lock sync.RWMutex - authChannel chan *pmapi.Auth - hasAPIAuth bool + lock sync.RWMutex + isAuthorized bool unlockingKeyringLock sync.Mutex wasKeyringUnlocked bool @@ -116,15 +115,6 @@ func (u *User) init(idleUpdates chan interface{}) (err error) { } u.creds = creds - // Set up the auth channel on which auths from the api client are sent. - u.authChannel = make(chan *pmapi.Auth) - u.client().SetAuths(u.authChannel) - u.hasAPIAuth = false - go func() { - defer u.panicHandler.HandlePanic() - u.watchAPIClientAuths() - }() - // Try to authorise the user if they aren't already authorised. // Note: we still allow users to set up bridge if the internet is off. if authErr := u.authorizeIfNecessary(false); authErr != nil { @@ -169,7 +159,7 @@ func (u *User) SetIMAPIdleUpdateChannel() { // authorizeIfNecessary checks whether user is logged in and is connected to api auth channel. // If user is not already connected to the api auth channel (for example there was no internet during start), -// it tries to connect it. See `connectToAuthChannel` for more info. +// it tries to connect it. func (u *User) authorizeIfNecessary(emitEvent bool) (err error) { // If user is connected and has an auth channel, then perfect, nothing to do here. if u.creds.IsConnected() && u.HasAPIAuth() { @@ -236,11 +226,9 @@ func (u *User) authorizeAndUnlock() (err error) { return nil } - auth, err := u.client().AuthRefresh(u.creds.APIToken) - if err != nil { + if _, err := u.client().AuthRefresh(u.creds.APIToken); err != nil { return errors.Wrap(err, "failed to refresh API auth") } - u.authChannel <- auth if _, err = u.client().Unlock(u.creds.MailboxPassword); err != nil { return errors.Wrap(err, "failed to unlock user") @@ -253,32 +241,34 @@ func (u *User) authorizeAndUnlock() (err error) { return nil } -// See `connectToAPIClientAuthChannel` for more info. -func (u *User) watchAPIClientAuths() { - for auth := range u.authChannel { - if auth != nil { - newRefreshToken := auth.UID() + ":" + auth.RefreshToken - u.updateAPIToken(newRefreshToken) - u.hasAPIAuth = true - } else if err := u.logout(); err != nil { +func (u *User) ReceiveAPIAuth(auth *pmapi.Auth) { + if auth == nil { + if err := u.logout(); err != nil { u.log.WithError(err).Error("Cannot logout user after receiving empty auth from API") } + u.isAuthorized = false + return } + + u.updateAPIToken(auth.GenToken()) } // updateAPIToken is helper for updating the token in keychain. It's not supposed to be -// called directly from other parts of the code--only from `watchAPIClientAuths`. +// called directly from other parts of the code, only from `ReceiveAPIAuth`. func (u *User) updateAPIToken(newRefreshToken string) { u.lock.Lock() defer u.lock.Unlock() - u.log.Info("Saving refresh token") + u.log.WithField("token", newRefreshToken).Info("Saving token to credentials store") if err := u.credStorer.UpdateToken(u.userID, newRefreshToken); err != nil { u.log.WithError(err).Error("Cannot update refresh token in credentials store") - } else { - u.refreshFromCredentials() + return } + + u.refreshFromCredentials() + + u.isAuthorized = true } // clearStore removes the database. @@ -548,18 +538,7 @@ func (u *User) Logout() (err error) { u.wasKeyringUnlocked = false u.unlockingKeyringLock.Unlock() - if err = u.client().Logout(); err != nil { - u.log.WithError(err).Warn("Could not log user out from API client") - } - u.client().SetAuths(nil) - - // Logout needs to stop auth channel so when user logs back in - // it can register again with new client. - // Note: be careful to not close channel twice. - if u.authChannel != nil { - close(u.authChannel) - u.authChannel = nil - } + u.client().Logout() if err = u.credStorer.Logout(u.userID); err != nil { u.log.WithError(err).Warn("Could not log user out from credentials store") @@ -617,5 +596,5 @@ func (u *User) GetStore() *store.Store { } func (u *User) HasAPIAuth() bool { - return u.hasAPIAuth + return u.isAuthorized } diff --git a/pkg/config/logs.go b/pkg/config/logs.go index 26c647c9..4e8870cd 100644 --- a/pkg/config/logs.go +++ b/pkg/config/logs.go @@ -66,7 +66,7 @@ func HandlePanic(cfg *Config, output string) { if !cfg.IsDevMode() { // TODO: Is it okay to just create a throwaway client like this? c := pmapi.NewClientManager(cfg.GetAPIConfig()).GetClient("no-user-id") - defer func() { _ = c.Logout() }() + defer c.Logout() if err := c.ReportSentryCrash(fmt.Errorf(output)); err != nil { log.Error("Sentry crash report failed: ", err) diff --git a/pkg/pmapi/auth.go b/pkg/pmapi/auth.go index a37b4704..dfd00421 100644 --- a/pkg/pmapi/auth.go +++ b/pkg/pmapi/auth.go @@ -21,6 +21,7 @@ import ( "crypto/subtle" "encoding/base64" "errors" + "fmt" "net/http" "strings" "time" @@ -122,6 +123,10 @@ func (s *Auth) UID() string { return s.uid } +func (s *Auth) GenToken() string { + return fmt.Sprintf("%v:%v", s.UID(), s.RefreshToken) +} + func (s *Auth) HasTwoFactor() bool { if s.TwoFA == nil { return false @@ -191,9 +196,16 @@ type AuthRefreshReq struct { State string } -// SetAuths sets auths channel. -func (c *Client) SetAuths(auths chan<- *Auth) { - c.auths = auths +func (c *Client) sendAuth(auth *Auth) { + c.cm.getClientAuthChannel() <- ClientAuth{ + UserID: c.userID, + Auth: auth, + } + + if auth != nil { + c.uid = auth.UID() + c.accessToken = auth.accessToken + } } // AuthInfo gets authentication info for a user. @@ -301,13 +313,7 @@ func (c *Client) Auth(username, password string, info *AuthInfo) (auth *Auth, er } auth = authRes.getAuth() - c.uid = auth.UID() - c.accessToken = auth.accessToken - - if c.auths != nil { - c.auths <- auth - } - c.cm.SetToken(c.userID, c.uid+":"+auth.RefreshToken) + c.sendAuth(auth) // Auth has to be fully unlocked to get key salt. During `Auth` it can happen // only to accounts without 2FA. For 2FA accounts, it's done in `Auth2FA`. @@ -403,7 +409,8 @@ func (c *Client) Unlock(password string) (kr *pmcrypto.KeyRing, err error) { func (c *Client) AuthRefresh(uidAndRefreshToken string) (auth *Auth, err error) { // If we don't yet have a saved access token, save this one in case the refresh fails! // That way we can try again later (see handleUnauthorizedStatus). - c.cm.SetTokenIfUnset(c.userID, uidAndRefreshToken) + // TODO: + // c.cm.SetTokenIfUnset(c.userID, uidAndRefreshToken) split := strings.Split(uidAndRefreshToken, ":") if len(split) != 2 { @@ -437,22 +444,18 @@ func (c *Client) AuthRefresh(uidAndRefreshToken string) (auth *Auth, err error) } auth = res.getAuth() - // UID should never change after auth, see backend-communication#11 - auth.uid = c.uid - if c.auths != nil { - c.auths <- auth - } - - c.uid = auth.UID() - c.accessToken = auth.accessToken - c.cm.SetToken(c.userID, c.uid+":"+res.RefreshToken) - + c.sendAuth(auth) c.expiresAt = time.Now().Add(time.Duration(auth.ExpiresIn) * time.Second) + return auth, err } -// Logout logs the current user out. -func (c *Client) Logout() (err error) { +func (c *Client) Logout() { + c.cm.LogoutClient(c.userID) +} + +// logout logs the current user out. +func (c *Client) logout() (err error) { req, err := NewRequest("DELETE", "/auth", nil) if err != nil { return @@ -467,23 +470,13 @@ func (c *Client) Logout() (err error) { return } - // This can trigger a deadlock! We don't want to do it if the above requests failed (GODT-154). - // That's why it's not in the deferred statement above. - if c.auths != nil { - c.auths <- nil - } + return +} - // This should ideally be deferred at the top of this method so that it is executed - // regardless of what happens, but we currently don't have a way to prevent ourselves - // from using a logged out client. So for now, it's down here, as it was in Charles release. - // defer func() { +func (c *Client) clearSensitiveData() { c.uid = "" c.accessToken = "" c.kr = nil - // c.addresses = nil + c.addresses = nil c.user = nil - c.cm.ClearToken(c.userID) - // }() - - return err } diff --git a/pkg/pmapi/client.go b/pkg/pmapi/client.go index f4df6626..e26ba92b 100644 --- a/pkg/pmapi/client.go +++ b/pkg/pmapi/client.go @@ -99,10 +99,8 @@ type ClientConfig struct { // Client to communicate with API. type Client struct { - auths chan<- *Auth // Channel that sends Auth responses back to the bridge. - - client *http.Client cm *ClientManager + client *http.Client uid string accessToken string @@ -121,39 +119,42 @@ type Client struct { // newClient creates a new API client. func newClient(cm *ClientManager, userID string) *Client { return &Client{ - log: logrus.WithField("pkg", "pmapi").WithField("userID", userID), - client: getHTTPClient(cm.GetConfig()), cm: cm, + client: getHTTPClient(cm.GetConfig()), userID: userID, requestLocker: &sync.Mutex{}, keyLocker: &sync.Mutex{}, + log: logrus.WithField("pkg", "pmapi").WithField("userID", userID), } } // getHTTPClient returns a http client configured by the given client config. -func getHTTPClient(cfg *ClientConfig) *http.Client { - hc := &http.Client{ - Timeout: cfg.Timeout, +func getHTTPClient(cfg *ClientConfig) (hc *http.Client) { + hc = &http.Client{Timeout: cfg.Timeout} + + if cfg.Transport == nil && defaultTransport == nil { + return } - if cfg.Transport != nil { - cfgTransport, ok := cfg.Transport.(*http.Transport) - if ok { - // In future use Clone here. - // https://go-review.googlesource.com/c/go/+/174597/ - transport := &http.Transport{} - *transport = *cfgTransport //nolint - if transport.Proxy == nil { - transport.Proxy = http.ProxyFromEnvironment - } - hc.Transport = transport - } else { - hc.Transport = cfg.Transport - } - } else if defaultTransport != nil { + if defaultTransport != nil { hc.Transport = defaultTransport + return } + // In future use Clone here. + // https://go-review.googlesource.com/c/go/+/174597/ + if cfgTransport, ok := cfg.Transport.(*http.Transport); ok { + transport := &http.Transport{} + *transport = *cfgTransport //nolint + if transport.Proxy == nil { + transport.Proxy = http.ProxyFromEnvironment + } + hc.Transport = transport + return + } + + hc.Transport = cfg.Transport + return hc } @@ -400,30 +401,20 @@ func (c *Client) readAllMinSpeed(data io.Reader, cancelRequest context.CancelFun func (c *Client) refreshAccessToken() (err error) { c.log.Debug("Refreshing token") + refreshToken := c.cm.GetToken(c.userID) - c.log.WithField("token", refreshToken).Info("Current refresh token") + if refreshToken == "" { - if c.auths != nil { - c.auths <- nil - } - c.cm.ClearToken(c.userID) + c.sendAuth(nil) return ErrInvalidToken } - auth, err := c.AuthRefresh(refreshToken) - if err != nil { - c.log.WithError(err).WithField("auths", c.auths).Debug("Token refreshing failed") - // The refresh failed, so we should log the user out. - // A nil value in the Auths channel will trigger this. - if c.auths != nil { - c.auths <- nil - } - c.cm.ClearToken(c.userID) - return + if _, err := c.AuthRefresh(refreshToken); err != nil { + c.sendAuth(nil) + return err } - c.uid = auth.UID() - c.accessToken = auth.accessToken - return err + + return } func (c *Client) handleStatusUnauthorized(req *http.Request, reqBodyBuffer []byte, res *http.Response, retry bool) (retryRes *http.Response, err error) { diff --git a/pkg/pmapi/clientmanager.go b/pkg/pmapi/clientmanager.go index ef9925ae..83aec19e 100644 --- a/pkg/pmapi/clientmanager.go +++ b/pkg/pmapi/clientmanager.go @@ -1,30 +1,50 @@ package pmapi import ( + "sync" + "github.com/getsentry/raven-go" "github.com/sirupsen/logrus" ) // ClientManager is a manager of clients. type ClientManager struct { - // TODO: Lockers. + clients map[string]*Client + clientsLocker sync.Locker - clients map[string]*Client - tokens map[string]string - config *ClientConfig + tokens map[string]string + tokensLocker sync.Locker + + config *ClientConfig + + bridgeAuths chan ClientAuth + clientAuths chan ClientAuth +} + +type ClientAuth struct { + UserID string + Auth *Auth } // NewClientManager creates a new ClientMan which manages clients configured with the given client config. -func NewClientManager(config *ClientConfig) *ClientManager { +func NewClientManager(config *ClientConfig) (cm *ClientManager) { if err := raven.SetDSN(config.SentryDSN); err != nil { logrus.WithError(err).Error("Could not set up sentry DSN") } - return &ClientManager{ - clients: make(map[string]*Client), - tokens: make(map[string]string), - config: config, + cm = &ClientManager{ + clients: make(map[string]*Client), + clientsLocker: &sync.Mutex{}, + tokens: make(map[string]string), + tokensLocker: &sync.Mutex{}, + config: config, + bridgeAuths: make(chan ClientAuth), + clientAuths: make(chan ClientAuth), } + + go cm.forwardClientAuths() + + return } // GetClient returns a client for the given userID. @@ -39,6 +59,28 @@ func (cm *ClientManager) GetClient(userID string) *Client { return cm.clients[userID] } +// LogoutClient logs out the client with the given userID and ensures its sensitive data is successfully cleared. +func (cm *ClientManager) LogoutClient(userID string) { + client, ok := cm.clients[userID] + + if !ok { + return + } + + delete(cm.clients, userID) + + go func() { + if err := client.logout(); err != nil { + // TODO: Try again! + logrus.WithError(err).Error("Client logout failed, not trying again") + } + client.clearSensitiveData() + cm.clearToken(userID) + }() + + return +} + // GetConfig returns the config used to configure clients. func (cm *ClientManager) GetConfig() *ClientConfig { return cm.config @@ -49,21 +91,52 @@ func (cm *ClientManager) GetToken(userID string) string { return cm.tokens[userID] } -// SetToken sets the token for the given userID. -func (cm *ClientManager) SetToken(userID, token string) { +// GetBridgeAuthChannel returns a channel on which client auths can be received. +func (cm *ClientManager) GetBridgeAuthChannel() chan ClientAuth { + return cm.clientAuths +} + +// getClientAuthChannel returns a channel on which clients should send auths. +func (cm *ClientManager) getClientAuthChannel() chan ClientAuth { + return cm.clientAuths +} + +// forwardClientAuths handles all incoming auths from clients before forwarding them on the bridge auth channel. +func (cm *ClientManager) forwardClientAuths() { + for auth := range cm.clientAuths { + cm.handleClientAuth(auth) + cm.bridgeAuths <- auth + } +} + +func (cm *ClientManager) setToken(userID, token string) { + cm.tokensLocker.Lock() + defer cm.tokensLocker.Unlock() + + logrus.WithField("userID", userID).WithField("token", token).Info("Updating refresh token") + cm.tokens[userID] = token } -// SetTokenIfUnset sets the token for the given userID if it does not yet have a token. -func (cm *ClientManager) SetTokenIfUnset(userID, token string) { - if _, ok := cm.tokens[userID]; ok { +func (cm *ClientManager) clearToken(userID string) { + cm.tokensLocker.Lock() + defer cm.tokensLocker.Unlock() + + logrus.WithField("userID", userID).Info("Clearing refresh token") + + delete(cm.tokens, userID) +} + +// handleClientAuth +func (cm *ClientManager) handleClientAuth(ca ClientAuth) { + // TODO: Maybe want to logout the client in case of nil auth. + if _, ok := cm.clients[ca.UserID]; !ok { return } - cm.tokens[userID] = token -} - -// ClearToken clears the token of the given userID. -func (cm *ClientManager) ClearToken(userID string) { - delete(cm.tokens, userID) + if ca.Auth == nil { + cm.clearToken(ca.UserID) + } else { + cm.setToken(ca.UserID, ca.Auth.GenToken()) + } }