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