github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/engine/login.go (about)

     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  
     6  package engine
     7  
     8  import (
     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  
    17  var errNoConfig = errors.New("No user config available")
    18  var errNoDevice = errors.New("No device provisioned locally for this user")
    19  
    20  // Login is an engine.
    21  type 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.
    43  func 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.
    52  func 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.
    63  func (e *Login) Name() string {
    64  	return "Login"
    65  }
    66  
    67  // GetPrereqs returns the engine prereqs.
    68  func (e *Login) Prereqs() Prereqs {
    69  	return Prereqs{}
    70  }
    71  
    72  // RequiredUIs returns the required UIs.
    73  func (e *Login) RequiredUIs() []libkb.UIKind {
    74  	return []libkb.UIKind{}
    75  }
    76  
    77  // SubConsumers returns the other UI consumers for this engine.
    78  func (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.
    88  func (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  
   166  func (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.
   213  func (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  
   225  func (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.
   232  func (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  
   240  func (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  
   281  func (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  
   307  func (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  }