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  }