store.ChangePasswordNoWallet implement and test
This commit is contained in:
parent
41b65d08ab
commit
02a5b9ce24
2 changed files with 204 additions and 6 deletions
|
@ -8,6 +8,9 @@ import (
|
|||
"orblivion/lbry-id/wallet"
|
||||
)
|
||||
|
||||
// It involves both wallet and account tables. Should it go in wallet_test.go
|
||||
// or account_test.go? Decided to just make it its own file.
|
||||
|
||||
func TestStoreChangePasswordSuccess(t *testing.T) {
|
||||
s, sqliteTmpFile := StoreTestInit(t)
|
||||
defer StoreTestCleanup(sqliteTmpFile)
|
||||
|
@ -36,8 +39,8 @@ func TestStoreChangePasswordSuccess(t *testing.T) {
|
|||
sequence := wallet.Sequence(2)
|
||||
hmac := wallet.WalletHmac("my-hmac-2")
|
||||
|
||||
if err := s.ChangePassword(email, oldPassword, newPassword, encryptedWallet, sequence, hmac); err != nil {
|
||||
t.Errorf("ChangePassword: unexpected error: %+v", err)
|
||||
if err := s.ChangePasswordWithWallet(email, oldPassword, newPassword, encryptedWallet, sequence, hmac); err != nil {
|
||||
t.Errorf("ChangePasswordWithWallet: unexpected error: %+v", err)
|
||||
}
|
||||
|
||||
expectAccountMatch(t, &s, email, newPassword)
|
||||
|
@ -130,8 +133,8 @@ func TestStoreChangePasswordErrors(t *testing.T) {
|
|||
submittedOldPassword := oldPassword + tc.oldPasswordSuffix // Possibly make it the wrong password
|
||||
newPassword := oldPassword + auth.Password("_new") // Possibly make the new password different (as it should be)
|
||||
|
||||
if err := s.ChangePassword(submittedEmail, submittedOldPassword, newPassword, newEncryptedWallet, tc.sequence, newHmac); err != tc.expectedError {
|
||||
t.Errorf("ChangePassword: unexpected value for err. want: %+v, got: %+v", tc.expectedError, err)
|
||||
if err := s.ChangePasswordWithWallet(submittedEmail, submittedOldPassword, newPassword, newEncryptedWallet, tc.sequence, newHmac); err != tc.expectedError {
|
||||
t.Errorf("ChangePasswordWithWallet: unexpected value for err. want: %+v, got: %+v", tc.expectedError, err)
|
||||
}
|
||||
|
||||
// The password and wallet didn't change, the token didn't get deleted.
|
||||
|
@ -148,3 +151,119 @@ func TestStoreChangePasswordErrors(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreChangePasswordNoWalletSuccess(t *testing.T) {
|
||||
s, sqliteTmpFile := StoreTestInit(t)
|
||||
defer StoreTestCleanup(sqliteTmpFile)
|
||||
|
||||
userId, email, oldPassword := makeTestUser(t, &s)
|
||||
token := auth.TokenString("my-token")
|
||||
|
||||
_, err := s.db.Exec(
|
||||
"INSERT INTO auth_tokens (token, user_id, device_id, scope, expiration) VALUES(?,?,?,?,?)",
|
||||
token, userId, "my-dev-id", "*", time.Now().UTC().Add(time.Hour*24*14),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating token")
|
||||
}
|
||||
|
||||
newPassword := oldPassword + auth.Password("_new")
|
||||
|
||||
if err := s.ChangePasswordNoWallet(email, oldPassword, newPassword); err != nil {
|
||||
t.Errorf("ChangePasswordNoWallet: unexpected error: %+v", err)
|
||||
}
|
||||
|
||||
expectAccountMatch(t, &s, email, newPassword)
|
||||
expectWalletNotExists(t, &s, userId)
|
||||
expectTokenNotExists(t, &s, token)
|
||||
}
|
||||
|
||||
func TestStoreChangePasswordNoWalletErrors(t *testing.T) {
|
||||
tt := []struct {
|
||||
name string
|
||||
hasWallet bool
|
||||
emailSuffix auth.Email
|
||||
oldPasswordSuffix auth.Password
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "wrong email",
|
||||
hasWallet: false, // we don't have the wallet, as expected for this function
|
||||
emailSuffix: auth.Email("_wrong"), // the email is *incorrect*
|
||||
oldPasswordSuffix: auth.Password(""), // the password is correct
|
||||
expectedError: ErrWrongCredentials,
|
||||
}, {
|
||||
name: "wrong old password",
|
||||
hasWallet: false, // we don't have the wallet, as expected for this function
|
||||
emailSuffix: auth.Email(""), // the email is correct
|
||||
oldPasswordSuffix: auth.Password("_wrong"), // the old password is *incorrect*
|
||||
expectedError: ErrWrongCredentials,
|
||||
}, {
|
||||
name: "unexpected wallet",
|
||||
hasWallet: true, // we have a wallet which we shouldn't have at this point
|
||||
emailSuffix: auth.Email(""), // the email is correct
|
||||
oldPasswordSuffix: auth.Password(""), // the password is correct
|
||||
expectedError: ErrUnexpectedWallet,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s, sqliteTmpFile := StoreTestInit(t)
|
||||
defer StoreTestCleanup(sqliteTmpFile)
|
||||
|
||||
userId, email, oldPassword := makeTestUser(t, &s)
|
||||
expiration := time.Now().UTC().Add(time.Hour * 24 * 14)
|
||||
authToken := auth.AuthToken{
|
||||
Token: auth.TokenString("my-token"),
|
||||
DeviceId: auth.DeviceId("my-dev-id"),
|
||||
UserId: userId,
|
||||
Scope: auth.AuthScope("*"),
|
||||
Expiration: &expiration,
|
||||
}
|
||||
|
||||
_, err := s.db.Exec(
|
||||
"INSERT INTO auth_tokens (token, user_id, device_id, scope, expiration) VALUES(?,?,?,?,?)",
|
||||
authToken.Token, authToken.UserId, authToken.DeviceId, authToken.Scope, authToken.Expiration,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating token")
|
||||
}
|
||||
|
||||
// Only for error case
|
||||
encryptedWallet := wallet.EncryptedWallet("my-enc-wallet-old")
|
||||
hmac := wallet.WalletHmac("my-hmac-old")
|
||||
sequence := wallet.Sequence(1)
|
||||
|
||||
if tc.hasWallet {
|
||||
_, err := s.db.Exec(
|
||||
"INSERT INTO wallets (user_id, encrypted_wallet, sequence, hmac) VALUES(?,?,?,?)",
|
||||
userId, encryptedWallet, sequence, hmac,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating test wallet")
|
||||
}
|
||||
}
|
||||
|
||||
submittedEmail := email + tc.emailSuffix // Possibly make it the wrong email
|
||||
submittedOldPassword := oldPassword + tc.oldPasswordSuffix // Possibly make it the wrong password
|
||||
newPassword := oldPassword + auth.Password("_new") // Possibly make the new password different (as it should be)
|
||||
|
||||
if err := s.ChangePasswordNoWallet(submittedEmail, submittedOldPassword, newPassword); err != tc.expectedError {
|
||||
t.Errorf("ChangePasswordNoWallet: unexpected value for err. want: %+v, got: %+v", tc.expectedError, err)
|
||||
}
|
||||
|
||||
// The password and wallet (if any) didn't change, the token didn't get
|
||||
// deleted. This tests the transaction rollbacks in particular, given the
|
||||
// errors that are at a couple different stages of the txn, triggered by
|
||||
// these tests.
|
||||
expectAccountMatch(t, &s, email, oldPassword)
|
||||
if tc.hasWallet {
|
||||
expectWalletExists(t, &s, userId, encryptedWallet, sequence, hmac)
|
||||
} else {
|
||||
expectWalletNotExists(t, &s, userId)
|
||||
}
|
||||
expectTokenExists(t, &s, authToken)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,8 @@ var (
|
|||
// return ErrWrongSequence to begin with. But think about it.
|
||||
ErrNoWallet = fmt.Errorf("Wallet does not exist for this user at this sequence")
|
||||
|
||||
ErrWrongSequence = fmt.Errorf("Wallet could not be updated to this sequence")
|
||||
ErrUnexpectedWallet = fmt.Errorf("Wallet unexpectedly exist for this user")
|
||||
ErrWrongSequence = fmt.Errorf("Wallet could not be updated to this sequence")
|
||||
|
||||
ErrDuplicateEmail = fmt.Errorf("Email already exists for this user")
|
||||
ErrDuplicateAccount = fmt.Errorf("User already has an account")
|
||||
|
@ -385,7 +386,7 @@ func (s *Store) CreateAccount(email auth.Email, password auth.Password) (err err
|
|||
// Also delete all auth tokens to force clients to update their root password
|
||||
// to get a new token. This prevents other clients from posting a wallet
|
||||
// encrypted with the old key.
|
||||
func (s *Store) ChangePassword(
|
||||
func (s *Store) ChangePasswordWithWallet(
|
||||
email auth.Email,
|
||||
oldPassword auth.Password,
|
||||
newPassword auth.Password,
|
||||
|
@ -463,3 +464,81 @@ func (s *Store) ChangePassword(
|
|||
_, err = tx.Exec("DELETE FROM auth_tokens WHERE user_id=?", userId)
|
||||
return
|
||||
}
|
||||
|
||||
// Change password, but with no wallet currently saved. Since there's no
|
||||
// wallet saved, there's no wallet to update. The encryption key is moot.
|
||||
//
|
||||
// Also delete all auth tokens to force clients to update their root password
|
||||
// to get a new token. This prevents other clients from posting a wallet
|
||||
// encrypted with the old key.
|
||||
func (s *Store) ChangePasswordNoWallet(
|
||||
email auth.Email,
|
||||
oldPassword auth.Password,
|
||||
newPassword auth.Password,
|
||||
) (err error) {
|
||||
var userId auth.UserId
|
||||
|
||||
tx, err := s.db.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Lots of error conditions. Just defer this. However, we need to make sure to
|
||||
// make sure the variable `err` is set to the error before we return, instead
|
||||
// of doing `return <error>`.
|
||||
endTxn := func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
} else {
|
||||
tx.Commit()
|
||||
}
|
||||
}
|
||||
defer endTxn()
|
||||
|
||||
err = tx.QueryRow(
|
||||
"SELECT user_id from accounts WHERE email=? AND password=?",
|
||||
email, oldPassword.Obfuscate(),
|
||||
).Scan(&userId)
|
||||
if err == sql.ErrNoRows {
|
||||
err = ErrWrongCredentials
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res, err := tx.Exec(
|
||||
"UPDATE accounts SET password=? WHERE user_id=?",
|
||||
newPassword.Obfuscate(), userId,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
numRows, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if numRows != 1 {
|
||||
// Very unexpected error!
|
||||
err = fmt.Errorf("Password failed to update")
|
||||
return
|
||||
}
|
||||
|
||||
// Assert we have no wallet for this version of the password change function.
|
||||
var dummy string
|
||||
err = tx.QueryRow("SELECT 1 FROM wallets WHERE user_id=?", userId).Scan(&dummy)
|
||||
if err != sql.ErrNoRows {
|
||||
if err == nil {
|
||||
// We expected no rows
|
||||
err = ErrUnexpectedWallet
|
||||
return
|
||||
}
|
||||
// Some other error
|
||||
return
|
||||
}
|
||||
|
||||
// Don't care how many I delete here. Might even be zero. No login token while
|
||||
// changing password seems plausible.
|
||||
_, err = tx.Exec("DELETE FROM auth_tokens WHERE user_id=?", userId)
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue