2022-07-06 20:02:34 +02:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2022-08-27 17:37:09 +02:00
|
|
|
"log"
|
2022-07-06 20:02:34 +02:00
|
|
|
"net/http"
|
2022-08-27 17:37:09 +02:00
|
|
|
"time"
|
2022-07-06 20:02:34 +02:00
|
|
|
|
2022-08-22 18:05:53 +02:00
|
|
|
"lbryio/wallet-sync-server/auth"
|
2022-08-27 17:37:09 +02:00
|
|
|
"lbryio/wallet-sync-server/metrics"
|
2022-08-22 18:05:53 +02:00
|
|
|
"lbryio/wallet-sync-server/store"
|
|
|
|
"lbryio/wallet-sync-server/wallet"
|
2022-08-27 17:37:09 +02:00
|
|
|
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2022-07-06 20:02:34 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
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"`
|
2022-07-15 21:36:11 +02:00
|
|
|
ClientSaltSeed auth.ClientSaltSeed `json:"clientSaltSeed"`
|
2022-07-06 20:02:34 +02:00
|
|
|
}
|
|
|
|
|
2022-07-11 15:42:08 +02:00
|
|
|
func (r *ChangePasswordRequest) validate() error {
|
2022-07-06 20:02:34 +02:00
|
|
|
// 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)
|
|
|
|
|
2022-07-24 22:02:55 +02:00
|
|
|
if !r.Email.Validate() {
|
2022-07-15 21:36:11 +02:00
|
|
|
return fmt.Errorf("Invalid or missing 'email'")
|
2022-07-11 15:42:08 +02:00
|
|
|
}
|
2022-08-23 01:41:30 +02:00
|
|
|
if !r.OldPassword.Validate() {
|
|
|
|
return fmt.Errorf("Invalid or missing 'oldPassword'")
|
2022-07-11 15:42:08 +02:00
|
|
|
}
|
2022-08-23 01:41:30 +02:00
|
|
|
if !r.NewPassword.Validate() {
|
|
|
|
return fmt.Errorf("Invalid or missing 'newPassword'")
|
2022-07-11 15:42:08 +02:00
|
|
|
}
|
2022-07-15 21:36:11 +02:00
|
|
|
// Too bad we can't do this so easily with clientSaltSeed
|
2022-07-11 15:42:08 +02:00
|
|
|
if r.OldPassword == r.NewPassword {
|
|
|
|
return fmt.Errorf("'oldPassword' and 'newPassword' should not be the same")
|
|
|
|
}
|
2022-07-24 22:02:55 +02:00
|
|
|
if !r.ClientSaltSeed.Validate() {
|
2022-07-15 21:36:11 +02:00
|
|
|
return fmt.Errorf("Invalid or missing 'clientSaltSeed'")
|
|
|
|
}
|
2022-07-11 15:42:08 +02:00
|
|
|
if !walletPresent && !walletAbsent {
|
|
|
|
return fmt.Errorf("Fields 'encryptedWallet', 'sequence', and 'hmac' should be all non-empty and non-zero, or all omitted")
|
|
|
|
}
|
|
|
|
return nil
|
2022-07-06 20:02:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) changePassword(w http.ResponseWriter, req *http.Request) {
|
|
|
|
var changePasswordRequest ChangePasswordRequest
|
|
|
|
if !getPostData(w, req, &changePasswordRequest) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-07-26 17:18:43 +02:00
|
|
|
// 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.
|
|
|
|
|
2022-07-06 20:02:34 +02:00
|
|
|
var err error
|
2022-08-27 17:37:09 +02:00
|
|
|
var userId auth.UserId
|
2022-07-06 20:02:34 +02:00
|
|
|
if changePasswordRequest.EncryptedWallet != "" {
|
2022-08-27 17:37:09 +02:00
|
|
|
userId, err = s.store.ChangePasswordWithWallet(
|
2022-07-06 20:02:34 +02:00
|
|
|
changePasswordRequest.Email,
|
|
|
|
changePasswordRequest.OldPassword,
|
|
|
|
changePasswordRequest.NewPassword,
|
2022-07-15 21:36:11 +02:00
|
|
|
changePasswordRequest.ClientSaltSeed,
|
2022-07-06 20:02:34 +02:00
|
|
|
changePasswordRequest.EncryptedWallet,
|
|
|
|
changePasswordRequest.Sequence,
|
|
|
|
changePasswordRequest.Hmac)
|
|
|
|
if err == store.ErrWrongSequence {
|
|
|
|
errorJson(w, http.StatusConflict, "Bad sequence number or wallet does not exist")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else {
|
2022-08-27 17:37:09 +02:00
|
|
|
userId, err = s.store.ChangePasswordNoWallet(
|
2022-07-06 20:02:34 +02:00
|
|
|
changePasswordRequest.Email,
|
|
|
|
changePasswordRequest.OldPassword,
|
|
|
|
changePasswordRequest.NewPassword,
|
2022-07-15 21:36:11 +02:00
|
|
|
changePasswordRequest.ClientSaltSeed,
|
2022-07-06 20:02:34 +02:00
|
|
|
)
|
|
|
|
if err == store.ErrUnexpectedWallet {
|
|
|
|
errorJson(w, http.StatusConflict, "Wallet exists; need an updated wallet when changing password")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err == store.ErrWrongCredentials {
|
2022-07-29 21:52:23 +02:00
|
|
|
errorJson(w, http.StatusUnauthorized, "No match for email and/or password")
|
2022-07-06 20:02:34 +02:00
|
|
|
return
|
|
|
|
}
|
2022-07-26 17:18:43 +02:00
|
|
|
if err == store.ErrNotVerified {
|
|
|
|
errorJson(w, http.StatusUnauthorized, "Account is not verified")
|
|
|
|
return
|
|
|
|
}
|
2022-07-06 20:02:34 +02:00
|
|
|
if err != nil {
|
|
|
|
internalServiceErrorJson(w, err, "Error changing password")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-08-27 17:37:09 +02:00
|
|
|
// TODO - A socket connection request using an old auth token could still
|
|
|
|
// succeed in a race condition:
|
|
|
|
// * websocket handler: checkAuth passes with token
|
|
|
|
// * password change handler: change password, invalidate token
|
|
|
|
// * password change handler: send userRemove message
|
|
|
|
// * websocket manager: process userRemove message, ending all websocket connections for user
|
|
|
|
// * websocket handler: new websocket connection is established
|
|
|
|
//
|
|
|
|
// It would require the websocket handler to be very slow, but I don't want to
|
|
|
|
// rule it out.
|
|
|
|
//
|
|
|
|
// But a much more likely scenario could happen: the buffer on the userRemove
|
|
|
|
// channel could get full and it could time out, and not boot any of the
|
|
|
|
// users' clients.
|
|
|
|
//
|
|
|
|
// These aren't horribly important now since the only message is a
|
|
|
|
// notification that a new wallet version exists, but who knows what we
|
|
|
|
// could use websockets for. Maybe we start doing something crazy like
|
|
|
|
// updating the wallet over the channel, in which case we absolutely want
|
|
|
|
// to prevent an old client from doing so after a password change on
|
|
|
|
// another client.
|
|
|
|
//
|
|
|
|
// We'd have to think a fair amount about how to make these foolproof if it
|
|
|
|
// becomes important. Maybe we just pass the auth token to the websocket
|
|
|
|
// writer, and pass it to every wallet update db call, and have it check
|
|
|
|
// the auth token within the same transaction as the wallet update.
|
|
|
|
|
|
|
|
timeout := time.NewTicker(100 * time.Millisecond)
|
|
|
|
select {
|
|
|
|
case s.userRemove <- wsClientForUser{userId, nil}:
|
|
|
|
case <-timeout.C:
|
2022-09-21 22:23:31 +02:00
|
|
|
metrics.ErrorsCount.With(prometheus.Labels{"error_type": "ws-user-remove"}).Inc()
|
2022-08-27 17:37:09 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
timeout.Stop()
|
|
|
|
|
2022-07-06 20:02:34 +02:00
|
|
|
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))
|
2022-08-27 17:37:09 +02:00
|
|
|
log.Printf("User %s has changed their password", changePasswordRequest.Email)
|
2022-07-06 20:02:34 +02:00
|
|
|
}
|