github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/engine/signup_recovery_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 "errors" 8 "fmt" 9 "net/http" 10 "testing" 11 12 "github.com/keybase/client/go/libkb" 13 "github.com/keybase/client/go/protocol/keybase1" 14 "github.com/stretchr/testify/require" 15 context "golang.org/x/net/context" 16 ) 17 18 type signupAPIMock struct { 19 *libkb.NullMockAPI 20 t *testing.T 21 realAPI libkb.API 22 23 failKeyMulti bool 24 localTimeoutKeyMulti bool 25 failEverything bool 26 } 27 28 var _ libkb.API = (*signupAPIMock)(nil) 29 30 func (n *signupAPIMock) Post(m libkb.MetaContext, args libkb.APIArg) (*libkb.APIRes, error) { 31 n.t.Logf("signupAPIMock.Post: %s\n", args.Endpoint) 32 return n.realAPI.Post(m, args) 33 } 34 35 func (n *signupAPIMock) PostJSON(m libkb.MetaContext, args libkb.APIArg) (*libkb.APIRes, error) { 36 n.t.Logf("signupAPIMock.PostJSON: %s\n", args.Endpoint) 37 if n.failKeyMulti && args.Endpoint == "key/multi" { 38 n.failEverything = true 39 n.t.Logf("Got key/multi, failing the call (not sending to real API), subsequent calls will fail as well") 40 return nil, errors.New("Mock failure") 41 } 42 res, err := n.realAPI.PostJSON(m, args) 43 if n.localTimeoutKeyMulti && args.Endpoint == "key/multi" { 44 n.failEverything = true 45 n.t.Logf("Got key/multi, mocking a local timeout, all subsequent API calls will fail as well.") 46 return nil, errors.New("Mock local failure") 47 } 48 return res, err 49 } 50 51 func (n *signupAPIMock) Get(m libkb.MetaContext, args libkb.APIArg) (*libkb.APIRes, error) { 52 if n.failEverything { 53 return nil, errors.New("signupAPIMock simulated network error") 54 } 55 return n.realAPI.Get(m, args) 56 } 57 58 func (n *signupAPIMock) GetResp(m libkb.MetaContext, args libkb.APIArg) (*http.Response, func(), error) { 59 if n.failEverything { 60 return nil, func() {}, errors.New("signupAPIMock simulated network error") 61 } 62 return n.realAPI.GetResp(m, args) 63 } 64 65 func (n *signupAPIMock) GetDecode(m libkb.MetaContext, args libkb.APIArg, wrap libkb.APIResponseWrapper) error { 66 if n.failEverything { 67 return errors.New("signupAPIMock simulated network error") 68 } 69 return n.realAPI.GetDecode(m, args, wrap) 70 } 71 72 func (n *signupAPIMock) GetDecodeCtx(ctx context.Context, args libkb.APIArg, wrap libkb.APIResponseWrapper) error { 73 if n.failEverything { 74 return errors.New("signupAPIMock simulated network error") 75 } 76 return n.realAPI.GetDecodeCtx(ctx, args, wrap) 77 } 78 79 func nopwChangePassphrase(tc libkb.TestContext) error { 80 newPassphrase := "okokokok" 81 arg := &keybase1.PassphraseChangeArg{ 82 Passphrase: newPassphrase, 83 Force: true, 84 } 85 uis := libkb.UIs{ 86 SecretUI: &libkb.TestSecretUI{}, 87 } 88 eng := NewPassphraseChange(tc.G, arg) 89 m := libkb.NewMetaContextForTest(tc).WithUIs(uis) 90 return RunEngine2(m, eng) 91 } 92 93 func TestSecretStorePwhashAfterSignup(t *testing.T) { 94 // Ensure there are no leftovers in secret store after normal, successful 95 // signup. 96 97 tc := SetupEngineTest(t, "signup") 98 defer tc.Cleanup() 99 100 fu := NewFakeUserOrBust(tc.T, "su") 101 arg := MakeTestSignupEngineRunArg(fu) 102 arg.GenerateRandomPassphrase = true 103 arg.Passphrase = "" 104 arg.StoreSecret = true 105 uis := libkb.UIs{ 106 LogUI: tc.G.UI.GetLogUI(), 107 GPGUI: &gpgtestui{}, 108 SecretUI: fu.NewSecretUI(), 109 LoginUI: &libkb.TestLoginUI{Username: fu.Username}, 110 } 111 eng := NewSignupEngine(tc.G, &arg) 112 m := NewMetaContextForTest(tc).WithUIs(uis) 113 err := RunEngine2(m, eng) 114 require.NoError(t, err) 115 116 ss := tc.G.SecretStore() 117 mctx := libkb.NewMetaContextForTest(tc) 118 a, err1 := ss.RetrieveSecret(mctx, libkb.NormalizedUsername(fmt.Sprintf("%s.tmp_eddsa", fu.Username))) 119 b, err2 := ss.RetrieveSecret(mctx, libkb.NormalizedUsername(fmt.Sprintf("%s.tmp_pwhash", fu.Username))) 120 require.True(t, a.IsNil()) 121 require.True(t, b.IsNil()) 122 require.Error(t, err1) 123 require.Error(t, err2) 124 } 125 126 func TestSignupFailProvision(t *testing.T) { 127 // Test recovery after NOPW SignupJoin succeeds but we fail to provision. 128 tc := SetupEngineTest(t, "signup") 129 defer tc.Cleanup() 130 131 fakeAPI := &signupAPIMock{t: t, realAPI: tc.G.API} 132 tc.G.API = fakeAPI 133 fakeAPI.failKeyMulti = true 134 135 fu := NewFakeUserOrBust(tc.T, "su") 136 tc.G.Log.Debug("New test user: %s / %s", fu.Username, fu.Email) 137 arg := MakeTestSignupEngineRunArg(fu) 138 arg.GenerateRandomPassphrase = true 139 arg.Passphrase = "" 140 arg.StoreSecret = true 141 fu.DeviceName = arg.DeviceName 142 143 // Try to sign up - we will fail because our key/multi request for 144 // provisioning will not go through. We should be able to login+provision 145 // afterwards with stored passphrase stream. 146 uis := libkb.UIs{ 147 LogUI: tc.G.UI.GetLogUI(), 148 GPGUI: &gpgtestui{}, 149 SecretUI: fu.NewSecretUI(), 150 LoginUI: &libkb.TestLoginUI{Username: fu.Username}, 151 } 152 s := NewSignupEngine(tc.G, &arg) 153 m := NewMetaContextForTest(tc).WithUIs(uis) 154 err := RunEngine2(m, s) 155 // We are expecting an error during signup. 156 require.Error(tc.T, err) 157 require.Contains(t, err.Error(), "Mock failure") 158 fu.EncryptionKey = s.encryptionKey 159 160 t.Logf("Signup failed with: %s", err) 161 require.True(t, fakeAPI.failEverything) 162 163 checkStoredPw := func() (foundA, foundB bool, err error) { 164 ss := tc.G.SecretStore() 165 mctx := libkb.NewMetaContextForTest(tc) 166 a, err1 := ss.RetrieveSecret(mctx, libkb.NormalizedUsername(fmt.Sprintf("%s.tmp_eddsa", fu.Username))) 167 b, err2 := ss.RetrieveSecret(mctx, libkb.NormalizedUsername(fmt.Sprintf("%s.tmp_pwhash", fu.Username))) 168 return !a.IsNil(), !b.IsNil(), libkb.CombineErrors(err1, err2) 169 } 170 171 // We expect to see stored eddsa and pwhash after signup. 172 foundA, foundB, err := checkStoredPw() 173 require.NoError(t, err) 174 require.True(t, foundA && foundB) 175 176 // We do not expect to see them in GetUsersWithStoredSecrets 177 users, err := tc.G.GetUsersWithStoredSecrets(context.Background()) 178 require.NoError(t, err) 179 require.Empty(t, users) 180 181 // Restore real API access. 182 tc.G.API = fakeAPI.realAPI 183 184 t.Logf("Trying to login after failed signup") 185 err = fu.Login(tc.G) 186 require.NoError(t, err) // This will not work - user has already devices provisioned, no way to recover. 187 188 // After signing up, we expect pw secret store entries to be cleared up. 189 foundA, foundB, err = checkStoredPw() 190 require.Error(t, err) 191 require.True(t, !foundA && !foundB) 192 193 // Try to post a link to see if things work. 194 _, _, err = runTrack(tc, fu, "t_alice", libkb.GetDefaultSigVersion(tc.G)) 195 require.NoError(t, err) 196 197 // See if user can set passphrase 198 err = nopwChangePassphrase(tc) 199 require.NoError(t, err) 200 201 { 202 eng := NewPassphraseCheck(tc.G, &keybase1.PassphraseCheckArg{ 203 Passphrase: "okokokok", 204 }) 205 err := RunEngine2(m, eng) 206 require.NoError(t, err) 207 } 208 } 209 210 func TestSignupFailAfterProvision(t *testing.T) { 211 tc := SetupEngineTest(t, "signup") 212 defer tc.Cleanup() 213 214 fakeAPI := &signupAPIMock{t: t, realAPI: tc.G.API} 215 tc.G.API = fakeAPI 216 fakeAPI.localTimeoutKeyMulti = true 217 218 fu := NewFakeUserOrBust(tc.T, "su") 219 tc.G.Log.Debug("New test user: %s / %s", fu.Username, fu.Email) 220 arg := MakeTestSignupEngineRunArg(fu) 221 arg.GenerateRandomPassphrase = true 222 arg.Passphrase = "" 223 arg.StoreSecret = true 224 fu.DeviceName = arg.DeviceName 225 226 // Try to sign up - we will fail because our provisioning request will go 227 // through but the response will not get back to us. So we are provisioned, 228 // but our device does not know about this. 229 uis := libkb.UIs{ 230 LogUI: tc.G.UI.GetLogUI(), 231 GPGUI: &gpgtestui{}, 232 SecretUI: fu.NewSecretUI(), 233 LoginUI: &libkb.TestLoginUI{Username: fu.Username}, 234 } 235 s := NewSignupEngine(tc.G, &arg) 236 m := NewMetaContextForTest(tc).WithUIs(uis) 237 err := RunEngine2(m, s) 238 // We are expecting an error during signup. 239 require.Error(tc.T, err) 240 require.Contains(t, err.Error(), "Mock local failure") 241 fu.EncryptionKey = s.encryptionKey 242 243 t.Logf("Signup failed with: %s", err) 244 require.True(t, fakeAPI.failEverything) 245 246 checkStoredPw := func() (foundA, foundB bool, err error) { 247 ss := tc.G.SecretStore() 248 mctx := libkb.NewMetaContextForTest(tc) 249 a, err1 := ss.RetrieveSecret(mctx, libkb.NormalizedUsername(fmt.Sprintf("%s.tmp_eddsa", fu.Username))) 250 b, err2 := ss.RetrieveSecret(mctx, libkb.NormalizedUsername(fmt.Sprintf("%s.tmp_pwhash", fu.Username))) 251 return !a.IsNil(), !b.IsNil(), libkb.CombineErrors(err1, err2) 252 } 253 254 // We expect to see stored eddsa and pwhash after signup. 255 foundA, foundB, err := checkStoredPw() 256 require.NoError(t, err) 257 require.True(t, foundA && foundB) 258 259 // Restore real API access. 260 tc.G.API = fakeAPI.realAPI 261 262 t.Logf("Trying to login after failed signup") 263 // This will not work - user has already devices provisioned, no way to recover. 264 // There is another ticket to make this work using stored secrets. 265 err = fu.Login(tc.G) 266 require.Error(t, err) 267 require.Contains(t, err.Error(), "Provision unavailable as you don't have access to any of your devices") 268 }