wallet-sync-server/server/password.go
Daniel Krol aee351a2b1 Don't allow password change for unverified accounts
Mainly because wallet change is tied up in it
2022-07-26 11:18:43 -04:00

121 lines
4.1 KiB
Go

package server
import (
"encoding/json"
"fmt"
"net/http"
"lbryio/lbry-id/auth"
"lbryio/lbry-id/store"
"lbryio/lbry-id/wallet"
)
type ChangePasswordRequest struct {
EncryptedWallet wallet.EncryptedWallet `json:"encryptedWallet"`
Sequence wallet.Sequence `json:"sequence"`
Hmac wallet.WalletHmac `json:"hmac"`
Email auth.Email `json:"email"`
OldPassword auth.Password `json:"oldPassword"`
NewPassword auth.Password `json:"newPassword"`
ClientSaltSeed auth.ClientSaltSeed `json:"clientSaltSeed"`
}
func (r *ChangePasswordRequest) validate() error {
// The wallet should be here or not. Not partially here.
walletPresent := (r.EncryptedWallet != "" && r.Hmac != "" && r.Sequence > 0)
walletAbsent := (r.EncryptedWallet == "" && r.Hmac == "" && r.Sequence == 0)
if !r.Email.Validate() {
return fmt.Errorf("Invalid or missing 'email'")
}
if r.OldPassword == "" {
return fmt.Errorf("Missing 'oldPassword'")
}
if r.NewPassword == "" {
return fmt.Errorf("Missing 'newPassword'")
}
// Too bad we can't do this so easily with clientSaltSeed
if r.OldPassword == r.NewPassword {
return fmt.Errorf("'oldPassword' and 'newPassword' should not be the same")
}
if !r.ClientSaltSeed.Validate() {
return fmt.Errorf("Invalid or missing 'clientSaltSeed'")
}
if !walletPresent && !walletAbsent {
return fmt.Errorf("Fields 'encryptedWallet', 'sequence', and 'hmac' should be all non-empty and non-zero, or all omitted")
}
return nil
}
func (s *Server) changePassword(w http.ResponseWriter, req *http.Request) {
var changePasswordRequest ChangePasswordRequest
if !getPostData(w, req, &changePasswordRequest) {
return
}
// To be cautious, we will block password changes for unverified accounts.
// The only reason I can think of for allowing them is if the user
// accidentally put in a bad password that they desperately want to change,
// and the verification email isn't working. However unlikely such a scenario
// is, with the salting and the KDF and all that, it seems all the less a big
// deal.
//
// Changing a password when unverified as such isn't a big deal, but I'm
// concerned with wallet creation. This endpoint currently doesn't allow you
// to _create_ a wallet if you don't already have one, so as of now we don't
// strictly need this restriction. However this seems too precarious and
// tricky. We might forget about it and allow wallet creation here later.
// Someone might find a loophole I'm not thinking of. So I'm just blocking
// unverified accounts here for simplicity.
var err error
if changePasswordRequest.EncryptedWallet != "" {
err = s.store.ChangePasswordWithWallet(
changePasswordRequest.Email,
changePasswordRequest.OldPassword,
changePasswordRequest.NewPassword,
changePasswordRequest.ClientSaltSeed,
changePasswordRequest.EncryptedWallet,
changePasswordRequest.Sequence,
changePasswordRequest.Hmac)
if err == store.ErrWrongSequence {
errorJson(w, http.StatusConflict, "Bad sequence number or wallet does not exist")
return
}
} else {
err = s.store.ChangePasswordNoWallet(
changePasswordRequest.Email,
changePasswordRequest.OldPassword,
changePasswordRequest.NewPassword,
changePasswordRequest.ClientSaltSeed,
)
if err == store.ErrUnexpectedWallet {
errorJson(w, http.StatusConflict, "Wallet exists; need an updated wallet when changing password")
return
}
}
if err == store.ErrWrongCredentials {
errorJson(w, http.StatusUnauthorized, "No match for email and password")
return
}
if err == store.ErrNotVerified {
errorJson(w, http.StatusUnauthorized, "Account is not verified")
return
}
if err != nil {
internalServiceErrorJson(w, err, "Error changing password")
return
}
var changePasswordResponse struct{} // no data to respond with, but keep it JSON
var response []byte
response, err = json.Marshal(changePasswordResponse)
if err != nil {
internalServiceErrorJson(w, err, "Error generating change password response")
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, string(response))
}