1// Copyright 2015 Keybase, Inc. All rights reserved. Use of 2// this source code is governed by the included BSD license. 3 4// This is the main login engine. 5 6package engine 7 8import ( 9 "errors" 10 "fmt" 11 "strings" 12 13 "github.com/keybase/client/go/libkb" 14 keybase1 "github.com/keybase/client/go/protocol/keybase1" 15) 16 17var errNoConfig = errors.New("No user config available") 18var errNoDevice = errors.New("No device provisioned locally for this user") 19 20// Login is an engine. 21type Login struct { 22 libkb.Contextified 23 deviceType keybase1.DeviceTypeV2 24 username string 25 clientType keybase1.ClientType 26 27 doUserSwitch bool 28 29 // Used for non-interactive provisioning 30 PaperKey string 31 DeviceName string 32 33 // Used in tests for reproducible key generation 34 naclSigningKeyPair libkb.NaclKeyPair 35 naclEncryptionKeyPair libkb.NaclKeyPair 36 37 resetPending bool 38} 39 40// NewLogin creates a Login engine. username is optional. 41// deviceType should be keybase1.DeviceTypeV2_DESKTOP or 42// keybase1.DeviceTypeV2_MOBILE. 43func NewLogin(g *libkb.GlobalContext, deviceType keybase1.DeviceTypeV2, username string, ct keybase1.ClientType) *Login { 44 return NewLoginWithUserSwitch(g, deviceType, username, ct, false) 45} 46 47// NewLoginWithUserSwitch creates a Login engine. username is optional. 48// deviceType should be keybase1.DeviceTypeV2_DESKTOP or keybase1.DeviceTypeV2_MOBILE. 49// You can also specify a bool to say whether you'd like to doUserSwitch or not. 50// By default, this flag is off (see above), but as we roll out user switching, 51// we can start to turn this on in more places. 52func NewLoginWithUserSwitch(g *libkb.GlobalContext, deviceType keybase1.DeviceTypeV2, username string, ct keybase1.ClientType, doUserSwitch bool) *Login { 53 return &Login{ 54 Contextified: libkb.NewContextified(g), 55 deviceType: deviceType, 56 username: strings.TrimSpace(username), 57 clientType: ct, 58 doUserSwitch: doUserSwitch, 59 } 60} 61 62// Name is the unique engine name. 63func (e *Login) Name() string { 64 return "Login" 65} 66 67// GetPrereqs returns the engine prereqs. 68func (e *Login) Prereqs() Prereqs { 69 return Prereqs{} 70} 71 72// RequiredUIs returns the required UIs. 73func (e *Login) RequiredUIs() []libkb.UIKind { 74 return []libkb.UIKind{} 75} 76 77// SubConsumers returns the other UI consumers for this engine. 78func (e *Login) SubConsumers() []libkb.UIConsumer { 79 return []libkb.UIConsumer{ 80 &LoginProvisionedDevice{}, 81 &loginLoadUser{}, 82 &loginProvision{}, 83 &AccountReset{}, 84 } 85} 86 87// Run starts the engine. 88func (e *Login) Run(m libkb.MetaContext) (err error) { 89 m = m.WithLogTag("LOGIN") 90 defer m.Trace("Login#Run", &err)() 91 92 if len(e.username) > 0 && libkb.CheckEmail.F(e.username) { 93 // We used to support logging in with e-mail but we don't anymore, 94 // since 2019-03-20.(CORE-10470). 95 return libkb.NewBadUsernameErrorWithFullMessage("Logging in with e-mail address is not supported") 96 } 97 98 // check to see if already logged in 99 var loggedInOK bool 100 loggedInOK, err = e.checkLoggedInAndNotRevoked(m) 101 if err != nil { 102 m.Debug("Login: error checking if user is logged in: %s", err) 103 return err 104 } 105 if loggedInOK { 106 return nil 107 } 108 m.Debug("Login: not currently logged in") 109 110 // First see if this device is already provisioned and it is possible to log in. 111 loggedInOK, err = e.loginProvisionedDevice(m, e.username) 112 if err != nil { 113 m.Debug("loginProvisionedDevice error: %s", err) 114 115 // Suggest autoreset if user failed to log in and we're provisioned 116 if _, ok := err.(libkb.PassphraseError); ok { 117 return e.suggestRecoveryForgotPassword(m) 118 } 119 120 return err 121 } 122 if loggedInOK { 123 m.Debug("loginProvisionedDevice success") 124 return nil 125 } 126 127 m.Debug("loginProvisionedDevice failed, continuing with device provisioning") 128 129 // clear out any existing session: 130 m.Debug("clearing any existing login session with Logout before loading user for login") 131 // If the doUserSwitch flag is specified, we don't want to kill the existing session 132 err = m.LogoutWithOptions(libkb.LogoutOptions{KeepSecrets: e.doUserSwitch}) 133 if err != nil { 134 return err 135 } 136 137 // Set up a provisional login context for the purposes of running provisioning. 138 // This is where we'll store temporary session tokens, etc, that are useful 139 // in the context of this provisioning session. 140 m = m.WithNewProvisionalLoginContext() 141 defer func() { 142 if err == nil { 143 // resets the LoginContext to be nil, and also commits cacheable 144 // data like the passphrase stream into the global context. 145 m = m.CommitProvisionalLogin() 146 } 147 }() 148 149 resetPending, err := e.loginProvision(m) 150 if err != nil { 151 return err 152 } 153 if resetPending { 154 // We've just started a reset process 155 e.resetPending = true 156 return nil 157 } 158 159 e.perUserKeyUpgradeSoft(m) 160 161 m.Debug("Login provisioning success, sending login notification") 162 e.sendNotification(m) 163 return nil 164} 165 166func (e *Login) loginProvision(m libkb.MetaContext) (bool, error) { 167 m.Debug("loading login user for %q", e.username) 168 ueng := newLoginLoadUser(m.G(), e.username) 169 if err := RunEngine2(m, ueng); err != nil { 170 return false, err 171 } 172 173 if ueng.User().HasCurrentDeviceInCurrentInstall() { 174 // Somehow after loading a user we discovered that we are already 175 // provisioned. This should not happen. 176 m.Debug("loginProvisionedDevice after loginLoadUser (and user had current deivce in current install), failed to login [unexpected]") 177 return false, libkb.DeviceAlreadyProvisionedError{} 178 } 179 180 m.Debug("attempting device provisioning") 181 182 darg := &loginProvisionArg{ 183 DeviceType: e.deviceType, 184 ClientType: e.clientType, 185 User: ueng.User(), 186 187 PaperKey: e.PaperKey, 188 DeviceName: e.DeviceName, 189 190 naclSigningKeyPair: e.naclSigningKeyPair, 191 naclEncryptionKeyPair: e.naclEncryptionKeyPair, 192 } 193 deng := newLoginProvision(m.G(), darg) 194 if err := RunEngine2(m, deng); err != nil { 195 return false, err 196 } 197 198 // Skip notifications if we haven't provisioned 199 if !deng.LoggedIn() { 200 return true, nil 201 } 202 203 // If account was reset, rerun the provisioning with the existing session 204 if deng.AccountReset() { 205 return e.loginProvision(m) 206 } 207 208 return false, nil 209} 210 211// notProvisionedErr will return true if err signifies that login 212// failed because this device has not yet been provisioned. 213func (e *Login) notProvisionedErr(m libkb.MetaContext, err error) bool { 214 if err == errNoDevice { 215 return true 216 } 217 if err == errNoConfig { 218 return true 219 } 220 221 m.Debug("notProvisioned, not handling error %s (err type: %T)", err, err) 222 return false 223} 224 225func (e *Login) sendNotification(m libkb.MetaContext) { 226 m.G().NotifyRouter.HandleLogin(m.Ctx(), string(m.G().Env.GetUsername())) 227 m.G().CallLoginHooks(m) 228} 229 230// Get a per-user key. 231// Wait for attempt but only warn on error. 232func (e *Login) perUserKeyUpgradeSoft(m libkb.MetaContext) { 233 eng := NewPerUserKeyUpgrade(m.G(), &PerUserKeyUpgradeArgs{}) 234 err := RunEngine2(m, eng) 235 if err != nil { 236 m.Warning("loginProvision PerUserKeyUpgrade failed: %v", err) 237 } 238} 239 240func (e *Login) checkLoggedInAndNotRevoked(m libkb.MetaContext) (bool, error) { 241 m.Debug("checkLoggedInAndNotRevoked()") 242 243 username := libkb.NewNormalizedUsername(e.username) 244 245 // CheckForUsername() gets a consistent picture of the current active device, 246 // and sees if it matches the given username, and isn't revoked. If all goes 247 // well, we return `true,nil`. It could be we're already logged in but for 248 // someone else, in which case we return true and an error. 249 err := m.ActiveDevice().CheckForUsername(m, username, e.doUserSwitch) 250 251 switch err := err.(type) { 252 case nil: 253 return true, nil 254 case libkb.NoActiveDeviceError: 255 return false, nil 256 case libkb.UserNotFoundError: 257 m.Debug("Login: %s", err.Error()) 258 return false, err 259 case libkb.KeyRevokedError, libkb.DeviceNotFoundError: 260 m.Debug("Login on revoked or reset device: %s", err.Error()) 261 if err = m.LogoutUsernameWithOptions(username, libkb.LogoutOptions{KeepSecrets: false, Force: true}); err != nil { 262 m.Debug("logout error: %s", err) 263 } 264 return false, err 265 case libkb.LoggedInWrongUserError: 266 m.Debug(err.Error()) 267 if e.doUserSwitch { 268 err := m.LogoutKeepSecrets() 269 if err != nil { 270 return false, err 271 } 272 return false, nil 273 } 274 return true, libkb.LoggedInError{} 275 default: 276 m.Debug("Login: unexpected error: %s", err.Error()) 277 return false, fmt.Errorf("unexpected error in Login: %s", err.Error()) 278 } 279} 280 281func (e *Login) loginProvisionedDevice(m libkb.MetaContext, username string) (bool, error) { 282 eng := NewLoginProvisionedDevice(m.G(), username) 283 err := RunEngine2(m, eng) 284 // Whatever happened in the engine, overwrite our username with the one potentially 285 // chosen by the user. This gets rid of some confusing flows. 286 e.username = eng.GetUsername().String() 287 288 if err == nil { 289 // login successful 290 m.Debug("LoginProvisionedDevice.Run() was successful") 291 // Note: LoginProvisionedDevice Run() will send login notifications, no need to 292 // send here. 293 return true, nil 294 } 295 296 // if this device has been provisioned already and there was an error, then 297 // return that error. Otherwise, ignore it and keep going. 298 if !e.notProvisionedErr(m, err) { 299 return false, err 300 } 301 302 m.Debug("loginProvisionedDevice error: %s (not fatal, can continue to provision this device)", err) 303 304 return false, nil 305} 306 307func (e *Login) suggestRecoveryForgotPassword(mctx libkb.MetaContext) error { 308 enterReset, err := mctx.UIs().LoginUI.PromptResetAccount(mctx.Ctx(), keybase1.PromptResetAccountArg{ 309 Prompt: keybase1.NewResetPromptDefault(keybase1.ResetPromptType_ENTER_FORGOT_PW), 310 }) 311 if err != nil { 312 return err 313 } 314 if enterReset != keybase1.ResetPromptResponse_CONFIRM_RESET { 315 // Cancel the engine as the user decided to end the flow early. 316 return nil 317 } 318 319 // We are certain the user will not know their password, so we can disable the prompt. 320 eng := NewAccountReset(mctx.G(), e.username) 321 eng.skipPasswordPrompt = true 322 return eng.Run(mctx) 323} 324