github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/logout.go (about)

     1  package libkb
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/keybase/client/go/protocol/keybase1"
     8  )
     9  
    10  func (mctx MetaContext) LogoutKillSecrets() (err error) {
    11  	return mctx.LogoutWithOptions(LogoutOptions{KeepSecrets: false})
    12  }
    13  
    14  func (mctx MetaContext) LogoutKeepSecrets() (err error) {
    15  	return mctx.LogoutWithOptions(LogoutOptions{KeepSecrets: true})
    16  }
    17  
    18  type LogoutOptions struct {
    19  	KeepSecrets bool
    20  	Force       bool
    21  }
    22  
    23  func (mctx MetaContext) LogoutWithOptions(options LogoutOptions) (err error) {
    24  	username := mctx.ActiveDevice().Username(mctx)
    25  	return mctx.LogoutUsernameWithOptions(username, options)
    26  }
    27  
    28  func (mctx MetaContext) LogoutUsernameWithOptions(username NormalizedUsername, options LogoutOptions) (err error) {
    29  	mctx = mctx.WithLogTag("LOGOUT")
    30  	defer mctx.Trace(fmt.Sprintf("MetaContext#LogoutWithOptions(%#v)", options), &err)()
    31  
    32  	g := mctx.G()
    33  	defer g.switchUserMu.Acquire(mctx, "Logout")()
    34  
    35  	mctx.Debug("MetaContext#logoutWithSecretKill: after switchUserMu acquisition (username: %s, options: %#v)",
    36  		username, options)
    37  
    38  	if !options.Force && !options.KeepSecrets {
    39  		mctx.Debug("force=%t; keepSecrets=%t, so check if we're allowed to log out", options.Force, options.KeepSecrets)
    40  		mctx.Debug("MetaContext#logoutWithSecretKill: checking if CanLogout")
    41  		canLogoutRes := CanLogout(mctx)
    42  		mctx.Debug("MetaContext#logoutWithSecretKill: CanLogout res: %#v", canLogoutRes)
    43  		if !canLogoutRes.CanLogout {
    44  			return fmt.Errorf("Cannot log out: %s", canLogoutRes.Reason)
    45  		}
    46  	} else {
    47  		mctx.Debug("not checking if we are allowed to logout (force=%t, keepSecrets=%t)",
    48  			options.Force, options.KeepSecrets)
    49  	}
    50  
    51  	var keychainMode KeychainMode
    52  	keychainMode, err = g.ActiveDevice.ClearGetKeychainMode()
    53  	if err != nil {
    54  		return err
    55  	}
    56  
    57  	g.LocalSigchainGuard().Clear(mctx.Ctx(), "Logout")
    58  
    59  	mctx.Debug("+ MetaContext#logoutWithSecretKill: calling logout hooks")
    60  	g.CallLogoutHooks(mctx)
    61  	mctx.Debug("- MetaContext#logoutWithSecretKill: called logout hooks")
    62  
    63  	g.ClearPerUserKeyring()
    64  
    65  	// NB: This will acquire and release the cacheMu lock, so we have to make
    66  	// sure nothing holding a cacheMu ever looks for the switchUserMu lock.
    67  	g.FlushCaches()
    68  
    69  	if keychainMode == KeychainModeOS {
    70  		mctx.logoutSecretStore(username, options.KeepSecrets)
    71  	} else {
    72  		mctx.Debug("Not clearing secret store in mode %d", keychainMode)
    73  	}
    74  
    75  	// reload config to clear anything in memory
    76  	if err := g.ConfigReload(); err != nil {
    77  		mctx.Debug("Logout ConfigReload error: %s", err)
    78  	}
    79  
    80  	// send logout notification
    81  	g.NotifyRouter.HandleLogout(mctx.Ctx())
    82  
    83  	g.FeatureFlags.Clear()
    84  
    85  	g.IdentifyDispatch.OnLogout()
    86  
    87  	g.Identify3State.OnLogout()
    88  
    89  	err = g.GetUPAKLoader().OnLogout()
    90  	if err != nil {
    91  		return err
    92  	}
    93  
    94  	g.Pegboard.OnLogout(mctx)
    95  
    96  	return nil
    97  }
    98  
    99  func (mctx MetaContext) logoutSecretStore(username NormalizedUsername, keepSecrets bool) {
   100  
   101  	g := mctx.G()
   102  	g.secretStoreMu.Lock()
   103  	defer g.secretStoreMu.Unlock()
   104  
   105  	if g.secretStore == nil || username.IsNil() {
   106  		return
   107  	}
   108  
   109  	if keepSecrets {
   110  		g.switchedUsers[username] = true
   111  		return
   112  	}
   113  
   114  	if err := g.secretStore.ClearSecret(mctx, username); err != nil {
   115  		mctx.Debug("clear stored secret error: %s", err)
   116  		return
   117  	}
   118  
   119  	// If this user had previously switched into his account and wound up in the
   120  	// g.switchedUsers map (see just above), then now it's fine to delete them,
   121  	// since they are deleted from the secret store successfully.
   122  	delete(g.switchedUsers, username)
   123  }
   124  
   125  // LogoutSelfCheck checks with the API server to see if this uid+device pair should
   126  // logout.
   127  func (mctx MetaContext) LogoutSelfCheck() error {
   128  	g := mctx.G()
   129  	uid := g.ActiveDevice.UID()
   130  	if uid.IsNil() {
   131  		mctx.Debug("LogoutSelfCheck: no uid")
   132  		return nil
   133  	}
   134  	deviceID := g.ActiveDevice.DeviceID()
   135  	if deviceID.IsNil() {
   136  		mctx.Debug("LogoutSelfCheck: no device id")
   137  		return nil
   138  	}
   139  
   140  	arg := APIArg{
   141  		Endpoint: "selfcheck",
   142  		Args: HTTPArgs{
   143  			"uid":       S{Val: uid.String()},
   144  			"device_id": S{Val: deviceID.String()},
   145  		},
   146  		SessionType: APISessionTypeREQUIRED,
   147  	}
   148  	res, err := g.API.Post(mctx, arg)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	logout, err := res.Body.AtKey("logout").GetBool()
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	mctx.Debug("LogoutSelfCheck: should log out? %v", logout)
   159  	if logout {
   160  		mctx.Debug("LogoutSelfCheck: logging out...")
   161  		return mctx.LogoutKillSecrets()
   162  	}
   163  
   164  	return nil
   165  }
   166  
   167  func CanLogout(mctx MetaContext) (res keybase1.CanLogoutRes) {
   168  	if !mctx.G().ActiveDevice.Valid() {
   169  		mctx.Debug("CanLogout: looks like user is not logged in")
   170  		res.CanLogout = true
   171  		return res
   172  	}
   173  
   174  	if mctx.G().ActiveDevice.KeychainMode() == KeychainModeNone {
   175  		mctx.Debug("CanLogout: ok to logout since the key used doesn't user the keychain")
   176  		res.CanLogout = true
   177  		return res
   178  	}
   179  
   180  	mctx, cancel := mctx.WithTimeout(5 * time.Second)
   181  	defer cancel()
   182  	if err := CheckCurrentUIDDeviceID(mctx); err != nil {
   183  		switch err.(type) {
   184  		case DeviceNotFoundError, UserNotFoundError,
   185  			KeyRevokedError, NoDeviceError, NoUIDError:
   186  			mctx.Debug("CanLogout: allowing logout because of CheckCurrentUIDDeviceID returning: %s", err.Error())
   187  			return keybase1.CanLogoutRes{CanLogout: true}
   188  		default:
   189  			// Unexpected error like network connectivity issue, fall through.
   190  			// Even if we are offline here, we may be able to get cached value
   191  			// `keybase1.PassphraseState_KNOWN` from LoadPassphraseState and be allowed to log out.
   192  			mctx.Debug("CanLogout: CheckCurrentUIDDeviceID returned: %q, falling through", err.Error())
   193  		}
   194  	}
   195  
   196  	passphraseState, err := LoadPassphraseStateWithForceRepoll(mctx)
   197  
   198  	if err != nil {
   199  		return keybase1.CanLogoutRes{
   200  			CanLogout: false,
   201  			Reason:    fmt.Sprintf("We couldn't ensure that your account has a passphrase: %s", err.Error()),
   202  		}
   203  	}
   204  
   205  	if passphraseState == keybase1.PassphraseState_RANDOM {
   206  		return keybase1.CanLogoutRes{
   207  			CanLogout: false,
   208  			Reason:    "You signed up without a password and need to set a password first",
   209  		}
   210  	}
   211  
   212  	res.CanLogout = true
   213  	return res
   214  }