github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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 }