2022-06-09 23:04:49 +02:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2022-08-25 18:42:49 +02:00
|
|
|
"log"
|
2022-06-09 23:04:49 +02:00
|
|
|
"net/http"
|
2022-08-27 17:37:09 +02:00
|
|
|
"time"
|
2022-07-13 18:32:48 +02:00
|
|
|
|
2022-07-23 01:49:30 +02:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
|
2022-08-22 18:05:53 +02:00
|
|
|
"lbryio/wallet-sync-server/auth"
|
|
|
|
"lbryio/wallet-sync-server/metrics"
|
|
|
|
"lbryio/wallet-sync-server/store"
|
|
|
|
"lbryio/wallet-sync-server/wallet"
|
2022-06-09 23:04:49 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type WalletRequest struct {
|
2022-07-28 01:45:09 +02:00
|
|
|
Token auth.AuthTokenString `json:"token"`
|
2022-06-09 23:04:49 +02:00
|
|
|
EncryptedWallet wallet.EncryptedWallet `json:"encryptedWallet"`
|
|
|
|
Sequence wallet.Sequence `json:"sequence"`
|
|
|
|
Hmac wallet.WalletHmac `json:"hmac"`
|
|
|
|
}
|
|
|
|
|
2022-07-11 15:42:08 +02:00
|
|
|
func (r *WalletRequest) validate() error {
|
|
|
|
if r.Token == "" {
|
|
|
|
return fmt.Errorf("Missing 'token'")
|
|
|
|
}
|
|
|
|
if r.EncryptedWallet == "" {
|
|
|
|
return fmt.Errorf("Missing 'encryptedWallet'")
|
|
|
|
}
|
|
|
|
if r.Hmac == "" {
|
|
|
|
return fmt.Errorf("Missing 'hmac'")
|
|
|
|
}
|
2022-08-25 18:42:49 +02:00
|
|
|
if r.Sequence < store.InitialWalletSequence {
|
2022-07-11 15:42:08 +02:00
|
|
|
return fmt.Errorf("Missing or zero-value 'sequence'")
|
|
|
|
}
|
|
|
|
return nil
|
2022-06-09 23:04:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type WalletResponse struct {
|
|
|
|
EncryptedWallet wallet.EncryptedWallet `json:"encryptedWallet"`
|
|
|
|
Sequence wallet.Sequence `json:"sequence"`
|
|
|
|
Hmac wallet.WalletHmac `json:"hmac"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) handleWallet(w http.ResponseWriter, req *http.Request) {
|
|
|
|
if req.Method == http.MethodGet {
|
|
|
|
s.getWallet(w, req)
|
|
|
|
} else if req.Method == http.MethodPost {
|
|
|
|
s.postWallet(w, req)
|
|
|
|
} else {
|
|
|
|
errorJson(w, http.StatusMethodNotAllowed, "")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) getWallet(w http.ResponseWriter, req *http.Request) {
|
2022-09-21 22:23:31 +02:00
|
|
|
metrics.RequestsCount.With(prometheus.Labels{"method": "GET", "endpoint": "wallet"}).Inc()
|
2022-07-23 01:49:30 +02:00
|
|
|
|
2022-06-09 23:04:49 +02:00
|
|
|
if !getGetData(w, req) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-08-27 17:37:09 +02:00
|
|
|
token, paramsErr := getTokenParam(req)
|
2022-06-09 23:04:49 +02:00
|
|
|
|
|
|
|
if paramsErr != nil {
|
|
|
|
// In this specific case, the error is limited to values that are safe to
|
|
|
|
// give to the user.
|
|
|
|
errorJson(w, http.StatusBadRequest, paramsErr.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
authToken := s.checkAuth(w, token, auth.ScopeFull)
|
|
|
|
|
|
|
|
if authToken == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
latestEncryptedWallet, latestSequence, latestHmac, err := s.store.GetWallet(authToken.UserId)
|
|
|
|
|
|
|
|
if err == store.ErrNoWallet {
|
|
|
|
errorJson(w, http.StatusNotFound, "No wallet")
|
|
|
|
return
|
|
|
|
} else if err != nil {
|
|
|
|
internalServiceErrorJson(w, err, "Error retrieving wallet")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
walletResponse := WalletResponse{
|
|
|
|
EncryptedWallet: latestEncryptedWallet,
|
|
|
|
Sequence: latestSequence,
|
|
|
|
Hmac: latestHmac,
|
|
|
|
}
|
|
|
|
|
|
|
|
var response []byte
|
|
|
|
response, err = json.Marshal(walletResponse)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
internalServiceErrorJson(w, err, "Error generating wallet response")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintf(w, string(response))
|
|
|
|
}
|
|
|
|
|
2022-06-23 21:22:31 +02:00
|
|
|
// Response Code:
|
|
|
|
// 200: Update successful
|
|
|
|
// 409: Update unsuccessful due to new wallet's sequence not being 1 +
|
|
|
|
// current wallet's sequence
|
|
|
|
// 500: Update unsuccessful for unanticipated reasons
|
2022-06-09 23:04:49 +02:00
|
|
|
func (s *Server) postWallet(w http.ResponseWriter, req *http.Request) {
|
2022-09-21 22:23:31 +02:00
|
|
|
metrics.RequestsCount.With(prometheus.Labels{"method": "POST", "endpoint": "wallet"}).Inc()
|
2022-07-23 01:49:30 +02:00
|
|
|
|
2022-06-09 23:04:49 +02:00
|
|
|
var walletRequest WalletRequest
|
|
|
|
if !getPostData(w, req, &walletRequest) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
authToken := s.checkAuth(w, walletRequest.Token, auth.ScopeFull)
|
|
|
|
if authToken == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-06-23 21:22:31 +02:00
|
|
|
err := s.store.SetWallet(authToken.UserId, walletRequest.EncryptedWallet, walletRequest.Sequence, walletRequest.Hmac)
|
2022-06-09 23:04:49 +02:00
|
|
|
|
2022-06-23 21:22:31 +02:00
|
|
|
if err == store.ErrWrongSequence {
|
|
|
|
errorJson(w, http.StatusConflict, "Bad sequence number")
|
2022-06-09 23:04:49 +02:00
|
|
|
return
|
|
|
|
} else if err != nil {
|
|
|
|
// Something other than sequence error
|
2022-06-21 22:59:20 +02:00
|
|
|
internalServiceErrorJson(w, err, "Error saving or getting wallet")
|
2022-06-09 23:04:49 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-06-23 21:22:31 +02:00
|
|
|
var response []byte
|
2022-07-06 18:47:14 +02:00
|
|
|
var walletResponse struct{} // no data to respond with, but keep it JSON
|
2022-06-09 23:04:49 +02:00
|
|
|
response, err = json.Marshal(walletResponse)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
internalServiceErrorJson(w, err, "Error generating walletResponse")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-06-23 21:22:31 +02:00
|
|
|
fmt.Fprintf(w, string(response))
|
2022-08-25 18:42:49 +02:00
|
|
|
if walletRequest.Sequence == store.InitialWalletSequence {
|
|
|
|
log.Printf("Initial wallet created for user id %d", authToken.UserId)
|
|
|
|
}
|
2022-08-27 17:37:09 +02:00
|
|
|
|
|
|
|
// Inform the other clients over websockets. If we can't do it within 100
|
|
|
|
// milliseconds, don't bother. It's a nice-to-have, not mission critical.
|
|
|
|
// But, count the misses on the dashboard. If it happens a lot we should
|
|
|
|
// probably increase the buffer on the notify chans for the clients. Those
|
|
|
|
// will be a bottleneck within the socket manager.
|
|
|
|
timeout := time.NewTicker(100 * time.Millisecond)
|
|
|
|
select {
|
|
|
|
case s.walletUpdates <- walletUpdateMsg{authToken.UserId, walletRequest.Sequence}:
|
|
|
|
case <-timeout.C:
|
2022-09-21 22:23:31 +02:00
|
|
|
metrics.ErrorsCount.With(prometheus.Labels{"error_type": "ws-client-notify"}).Inc()
|
2022-08-27 17:37:09 +02:00
|
|
|
}
|
|
|
|
timeout.Stop()
|
2022-06-09 23:04:49 +02:00
|
|
|
}
|