wallet-sync-server/server/wallet.go
2022-07-22 19:49:30 -04:00

160 lines
4 KiB
Go

package server
import (
"encoding/json"
"fmt"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"lbryio/lbry-id/auth"
"lbryio/lbry-id/metrics"
"lbryio/lbry-id/store"
"lbryio/lbry-id/wallet"
)
type WalletRequest struct {
Token auth.TokenString `json:"token"`
EncryptedWallet wallet.EncryptedWallet `json:"encryptedWallet"`
Sequence wallet.Sequence `json:"sequence"`
Hmac wallet.WalletHmac `json:"hmac"`
}
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'")
}
if r.Sequence < 1 {
return fmt.Errorf("Missing or zero-value 'sequence'")
}
return nil
}
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, "")
}
}
// TODO - There's probably a struct-based solution here like with POST/PUT.
// We could put that struct up top as well.
func getWalletParams(req *http.Request) (token auth.TokenString, err error) {
tokenSlice, hasTokenSlice := req.URL.Query()["token"]
if !hasTokenSlice || tokenSlice[0] == "" {
err = fmt.Errorf("Missing token parameter")
}
if err == nil {
token = auth.TokenString(tokenSlice[0])
}
return
}
func (s *Server) getWallet(w http.ResponseWriter, req *http.Request) {
metrics.RequestsCount.With(prometheus.Labels{"method": "GET wallet"}).Inc()
if !getGetData(w, req) {
return
}
token, paramsErr := getWalletParams(req)
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))
}
// 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
func (s *Server) postWallet(w http.ResponseWriter, req *http.Request) {
metrics.RequestsCount.With(prometheus.Labels{"method": "POST wallet"}).Inc()
var walletRequest WalletRequest
if !getPostData(w, req, &walletRequest) {
return
}
authToken := s.checkAuth(w, walletRequest.Token, auth.ScopeFull)
if authToken == nil {
return
}
err := s.store.SetWallet(authToken.UserId, walletRequest.EncryptedWallet, walletRequest.Sequence, walletRequest.Hmac)
if err == store.ErrWrongSequence {
errorJson(w, http.StatusConflict, "Bad sequence number")
return
} else if err != nil {
// Something other than sequence error
internalServiceErrorJson(w, err, "Error saving or getting wallet")
return
}
var response []byte
var walletResponse struct{} // no data to respond with, but keep it JSON
response, err = json.Marshal(walletResponse)
if err != nil {
internalServiceErrorJson(w, err, "Error generating walletResponse")
return
}
fmt.Fprintf(w, string(response))
}