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 }