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

     1  // Copyright 2019 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package engine
     5  
     6  import (
     7  	"fmt"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/require"
    11  	context "golang.org/x/net/context"
    12  
    13  	"github.com/keybase/client/go/libkb"
    14  	"github.com/keybase/client/go/protocol/keybase1"
    15  )
    16  
    17  func TestPassphraseRecoverLoggedIn(t *testing.T) {
    18  	tc := SetupEngineTest(t, "PassphraseRecoverGuideAndReset")
    19  	defer tc.Cleanup()
    20  	u := CreateAndSignupFakeUser(tc, "pprec")
    21  
    22  	loginUI := &TestLoginUIRecover{}
    23  	uis := libkb.UIs{
    24  		LogUI:       tc.G.UI.GetLogUI(),
    25  		LoginUI:     loginUI,
    26  		SecretUI:    u.NewSecretUI(),
    27  		ProvisionUI: newTestProvisionUINoSecret(),
    28  	}
    29  	m := NewMetaContextForTest(tc).WithUIs(uis)
    30  
    31  	args := []keybase1.RecoverPassphraseArg{
    32  		// 1) Invalid username
    33  		{Username: "doesntexist"},
    34  		// 2) No username (last configured device)
    35  		{},
    36  		// 3) Valid username
    37  		{Username: u.Username},
    38  	}
    39  
    40  	for _, arg := range args {
    41  		// The args don't matter - passphrase recover does not work when you
    42  		// are logged in.
    43  		err := NewPassphraseRecover(tc.G, arg).Run(m)
    44  		require.Error(t, err)
    45  		require.IsType(t, err, libkb.LoggedInError{})
    46  	}
    47  }
    48  
    49  func TestPassphraseRecoverGuideAndReset(t *testing.T) {
    50  	tc := SetupEngineTest(t, "PassphraseRecoverGuideAndReset")
    51  	defer tc.Cleanup()
    52  	u := CreateAndSignupFakeUser(tc, "pprec")
    53  	Logout(tc)
    54  
    55  	// Here we're exploring a bunch of flows where the engine will explain to
    56  	// the user how to change their password. Eventually we'll enter the reset
    57  	// pipeline.
    58  
    59  	// We're starting off with all the required UIs
    60  	loginUI := &TestLoginUIRecover{}
    61  	uis := libkb.UIs{
    62  		LogUI:       tc.G.UI.GetLogUI(),
    63  		LoginUI:     loginUI,
    64  		SecretUI:    u.NewSecretUI(),
    65  		ProvisionUI: newTestProvisionUINoSecret(),
    66  	}
    67  	m := NewMetaContextForTest(tc).WithUIs(uis)
    68  
    69  	// With autoreset enabled we don't necessarily require the device to be
    70  	// preconfigured with an account, so instead of "NotProvisioned" we expect
    71  	// a "NotFound" here.
    72  	arg := keybase1.RecoverPassphraseArg{
    73  		Username: "doesntexist",
    74  	}
    75  	require.Equal(t, libkb.NotFoundError{},
    76  		NewPassphraseRecover(tc.G, arg).Run(m))
    77  
    78  	// Make sure that empty username shows the correct devices
    79  	arg.Username = ""
    80  	loginUI.chooseDevice = keybase1.DeviceTypeV2_DESKTOP
    81  	require.NoError(t, NewPassphraseRecover(tc.G, arg).Run(m))
    82  	require.Equal(t, keybase1.DeviceType_DESKTOP, loginUI.lastExplain.Kind)
    83  	require.Equal(t, defaultDeviceName, loginUI.lastExplain.Name)
    84  
    85  	// Expect same behaviour for an existing username
    86  	arg.Username = u.Username
    87  	require.NoError(t, NewPassphraseRecover(tc.G, arg).Run(m))
    88  	require.Equal(t, keybase1.DeviceType_DESKTOP, loginUI.lastExplain.Kind)
    89  	require.Equal(t, defaultDeviceName, loginUI.lastExplain.Name)
    90  
    91  	// Should work even for a user that isnt configured on the device
    92  	tc2 := SetupEngineTest(t, "PassphraseRecoverGuideAndReset2")
    93  	defer tc2.Cleanup()
    94  	u2 := CreateAndSignupFakeUser(tc2, "pprec")
    95  	arg.Username = u2.Username
    96  	require.NoError(t, NewPassphraseRecover(tc.G, arg).Run(m))
    97  	require.Equal(t, keybase1.DeviceType_DESKTOP, loginUI.lastExplain.Kind)
    98  	require.Equal(t, defaultDeviceName, loginUI.lastExplain.Name)
    99  
   100  	// You should be able to enter the pipeline without a password on both
   101  	// accounts.
   102  	loginUI.Reset()
   103  	loginUI.PassphraseRecovery = true
   104  	loginUI.ResetAccount = keybase1.ResetPromptResponse_CONFIRM_RESET
   105  	loginUI.chooseDevice = keybase1.DeviceTypeV2_NONE
   106  	m = NewMetaContextForTest(tc).WithUIs(uis)
   107  
   108  	arg.Username = u.Username
   109  	require.NoError(t, NewPassphraseRecover(tc.G, arg).Run(m))
   110  	require.Nil(t, loginUI.lastExplain)
   111  	require.Nil(t, assertAutoreset(tc, u.UID(), libkb.AutoresetEventStart))
   112  
   113  	arg.Username = u2.Username
   114  	require.NoError(t, NewPassphraseRecover(tc.G, arg).Run(m))
   115  	require.Nil(t, loginUI.lastExplain)
   116  	require.Nil(t, assertAutoreset(tc, u2.UID(), libkb.AutoresetEventStart))
   117  }
   118  
   119  func TestPassphraseRecoverPGPOnly(t *testing.T) {
   120  	tc := SetupEngineTest(t, "PassphraseRecoverPGPOnly")
   121  	defer tc.Cleanup()
   122  	u := createFakeUserWithPGPOnly(t, tc)
   123  
   124  	// If the only way to provision the account is to do it with a password,
   125  	// the flow should immediately go to autoreset.
   126  	loginUI := &TestLoginUIRecover{
   127  		TestLoginUI: libkb.TestLoginUI{
   128  			PassphraseRecovery: true,
   129  			ResetAccount:       keybase1.ResetPromptResponse_CONFIRM_RESET,
   130  		},
   131  	}
   132  	uis := libkb.UIs{
   133  		LogUI:       tc.G.UI.GetLogUI(),
   134  		LoginUI:     loginUI,
   135  		SecretUI:    u.NewSecretUI(),
   136  		ProvisionUI: newTestProvisionUINoSecret(),
   137  	}
   138  	m := NewMetaContextForTest(tc).WithUIs(uis)
   139  
   140  	arg := keybase1.RecoverPassphraseArg{
   141  		Username: u.Username,
   142  	}
   143  	require.NoError(t, NewPassphraseRecover(tc.G, arg).Run(m))
   144  	require.Nil(t, loginUI.lastExplain)
   145  
   146  	// Should be pending verification
   147  	require.Nil(t, assertAutoreset(tc, u.UID(), libkb.AutoresetEventStart))
   148  }
   149  
   150  func TestPassphraseRecoverNoDevices(t *testing.T) {
   151  	tc := SetupEngineTest(t, "PassphraseRecoverNoDevices")
   152  	defer tc.Cleanup()
   153  	username, passphrase := createFakeUserWithNoKeys(tc)
   154  
   155  	// If the only way to provision the account is to do it with a password,
   156  	// the flow should immediately go to autoreset.
   157  	loginUI := &TestLoginUIRecover{
   158  		TestLoginUI: libkb.TestLoginUI{
   159  			PassphraseRecovery: true,
   160  			ResetAccount:       keybase1.ResetPromptResponse_CONFIRM_RESET,
   161  		},
   162  	}
   163  	uis := libkb.UIs{
   164  		LogUI:       tc.G.UI.GetLogUI(),
   165  		LoginUI:     loginUI,
   166  		SecretUI:    &libkb.TestSecretUI{Passphrase: passphrase},
   167  		ProvisionUI: newTestProvisionUINoSecret(),
   168  	}
   169  	m := NewMetaContextForTest(tc).WithUIs(uis)
   170  
   171  	arg := keybase1.RecoverPassphraseArg{
   172  		Username: username,
   173  	}
   174  	require.NoError(t, NewPassphraseRecover(tc.G, arg).Run(m))
   175  	require.Nil(t, loginUI.lastExplain)
   176  
   177  	// Should not be in the reset queue
   178  	require.Nil(t, assertAutoreset(tc, libkb.UsernameToUID(username), -1))
   179  }
   180  
   181  func TestPassphraseRecoverChangeWithPaper(t *testing.T) {
   182  	tc1 := SetupEngineTest(t, "PassphraseRecoverChangeWithPaper")
   183  	defer tc1.Cleanup()
   184  
   185  	// Prepare two accounts on the same device
   186  	u1, paperkey1 := CreateAndSignupLPK(tc1, "pprec")
   187  	Logout(tc1)
   188  	u2, paperkey2 := CreateAndSignupLPK(tc1, "pprec")
   189  	Logout(tc1)
   190  
   191  	// And a third one on another one
   192  	tc2 := SetupEngineTest(t, "PassphraseRecoverChangeWithPaper")
   193  	defer tc2.Cleanup()
   194  	u3, paperkey3 := CreateAndSignupLPK(tc2, "pprec")
   195  	Logout(tc2)
   196  
   197  	loginUI := &TestLoginUIRecover{}
   198  	uis := libkb.UIs{
   199  		LogUI:   tc1.G.UI.GetLogUI(),
   200  		LoginUI: loginUI,
   201  		SecretUI: &TestSecretUIRecover{
   202  			T:        t,
   203  			PaperKey: paperkey2,
   204  			Password: "test1234",
   205  		},
   206  		ProvisionUI: newTestProvisionUI(),
   207  	}
   208  	m := NewMetaContextForTest(tc1).WithUIs(uis)
   209  	arg := keybase1.RecoverPassphraseArg{}
   210  
   211  	// should work with no username passed on tc1
   212  	arg.Username = ""
   213  	loginUI.chooseDevice = keybase1.DeviceTypeV2_PAPER
   214  	loginUI.Username = u2.Username
   215  	require.NoError(t, NewPassphraseRecover(tc1.G, arg).Run(m))
   216  	require.NoError(t, AssertLoggedIn(tc1))
   217  	require.NoError(t, AssertProvisioned(tc1))
   218  	Logout(tc1)
   219  
   220  	// should work the same way with a username passed
   221  	uis.SecretUI = &TestSecretUIRecover{
   222  		T:        t,
   223  		PaperKey: paperkey1,
   224  		Password: "test1234",
   225  	}
   226  	m = m.WithUIs(uis)
   227  	arg.Username = u1.Username
   228  	loginUI.Username = ""
   229  	require.NoError(t, NewPassphraseRecover(tc1.G, arg).Run(m))
   230  	require.NoError(t, AssertLoggedIn(tc1))
   231  	require.NoError(t, AssertProvisioned(tc1))
   232  	Logout(tc1)
   233  
   234  	// (3) should fail
   235  	uis.SecretUI = &TestSecretUIRecover{
   236  		T:        t,
   237  		PaperKey: paperkey3,
   238  		Password: "test1234",
   239  	}
   240  	loginUI = &TestLoginUIRecover{
   241  		chooseDevice: keybase1.DeviceTypeV2_PAPER,
   242  	}
   243  	uis.LoginUI = loginUI
   244  	m = m.WithUIs(uis)
   245  	arg.Username = u3.Username
   246  
   247  	require.NoError(t, NewPassphraseRecover(tc1.G, arg).Run(m))
   248  	require.Equal(t, 1, loginUI.calledChooseDevice)
   249  	for _, device := range loginUI.lastDevices {
   250  		require.NotEqual(t, keybase1.DeviceTypeV2_PAPER, device.Type)
   251  	}
   252  	require.Error(t, AssertLoggedIn(tc1))
   253  	require.Error(t, AssertProvisioned(tc1))
   254  	require.Nil(t, assertAutoreset(tc1, u3.UID(), -1))
   255  }
   256  
   257  type TestSecretUIRecover struct {
   258  	T                  *testing.T
   259  	PaperKey           string
   260  	Password           string
   261  	GetPassphraseCalls int
   262  }
   263  
   264  func (t *TestSecretUIRecover) GetPassphrase(p keybase1.GUIEntryArg, terminal *keybase1.SecretEntryArg) (keybase1.GetPassphraseRes, error) {
   265  	t.GetPassphraseCalls++
   266  
   267  	switch p.Type {
   268  	case keybase1.PassphraseType_PAPER_KEY:
   269  		return keybase1.GetPassphraseRes{
   270  			Passphrase:  t.PaperKey,
   271  			StoreSecret: false,
   272  		}, nil
   273  	case keybase1.PassphraseType_PASS_PHRASE,
   274  		keybase1.PassphraseType_VERIFY_PASS_PHRASE:
   275  		return keybase1.GetPassphraseRes{
   276  			Passphrase:  t.Password,
   277  			StoreSecret: false,
   278  		}, nil
   279  	default:
   280  		return keybase1.GetPassphraseRes{}, fmt.Errorf("Invalid passphrase type, got %v", p.Type)
   281  	}
   282  }
   283  
   284  type TestLoginUIRecover struct {
   285  	libkb.TestLoginUI
   286  
   287  	calledChooseDevice int
   288  	chooseDevice       keybase1.DeviceTypeV2
   289  	lastDevices        []keybase1.Device
   290  
   291  	lastExplain *keybase1.ExplainDeviceRecoveryArg
   292  }
   293  
   294  func (t *TestLoginUIRecover) ExplainDeviceRecovery(_ context.Context, arg keybase1.ExplainDeviceRecoveryArg) error {
   295  	t.lastExplain = &arg
   296  	return nil
   297  }
   298  
   299  func (t *TestLoginUIRecover) Reset() {
   300  	t.lastExplain = nil
   301  }
   302  
   303  func (t *TestLoginUIRecover) ChooseDeviceToRecoverWith(_ context.Context, arg keybase1.ChooseDeviceToRecoverWithArg) (keybase1.DeviceID, error) {
   304  	t.calledChooseDevice++
   305  	t.lastDevices = arg.Devices
   306  
   307  	if len(arg.Devices) == 0 || t.chooseDevice == keybase1.DeviceTypeV2_NONE {
   308  		return "", nil
   309  	}
   310  	for _, d := range arg.Devices {
   311  		if d.Type == t.chooseDevice {
   312  			return d.DeviceID, nil
   313  		}
   314  	}
   315  	return "", nil
   316  }