2fbcf6ee6d
A few things at once because it was faster to get a demo out the door. Skipping most test implementation though I made failing stubs so I know what to fill in later. * Get/Post WalletState * downloadKey/email so that a second client can log in, and/or recover from lost client * Test client in Python to demonstrate the above * Organize into packages
206 lines
5.9 KiB
Go
206 lines
5.9 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"orblivion/lbry-id/auth"
|
|
"orblivion/lbry-id/store"
|
|
"orblivion/lbry-id/wallet"
|
|
)
|
|
|
|
type WalletStateRequest struct {
|
|
Token auth.AuthTokenString `json:"token"`
|
|
BodyJSON string `json:"bodyJSON"`
|
|
PubKey auth.PublicKey `json:"publicKey"`
|
|
Signature auth.Signature `json:"signature"`
|
|
|
|
// downloadKey is derived from the same password used to encrypt the wallet.
|
|
// We want to keep it all in sync so we update it at the same time.
|
|
DownloadKey auth.DownloadKey `json:"downloadKey"`
|
|
}
|
|
|
|
func (r *WalletStateRequest) validate() bool {
|
|
return (r.Token != auth.AuthTokenString("") &&
|
|
r.BodyJSON != "" &&
|
|
r.PubKey != auth.PublicKey("") &&
|
|
r.Signature != auth.Signature(""))
|
|
}
|
|
|
|
type WalletStateResponse struct {
|
|
BodyJSON string `json:"bodyJSON"`
|
|
Signature auth.Signature `json:"signature"`
|
|
Error string `json:"error"` // in case of 409 Conflict responses. TODO - make field not show up if it's empty, to avoid confusion
|
|
}
|
|
|
|
func (s *Server) handleWalletState(w http.ResponseWriter, req *http.Request) {
|
|
if req.Method == http.MethodGet {
|
|
s.getWalletState(w, req)
|
|
} else if req.Method == http.MethodPost {
|
|
s.postWalletState(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 getWalletStateParams(req *http.Request) (pubKey auth.PublicKey, deviceId string, token auth.AuthTokenString, err error) {
|
|
tokenSlice, hasTokenSlice := req.URL.Query()["token"]
|
|
deviceIDSlice, hasDeviceId := req.URL.Query()["deviceId"]
|
|
pubKeySlice, hasPubKey := req.URL.Query()["publicKey"]
|
|
|
|
if !hasDeviceId {
|
|
err = fmt.Errorf("Missing deviceId parameter")
|
|
}
|
|
if !hasTokenSlice {
|
|
err = fmt.Errorf("Missing token parameter")
|
|
}
|
|
if !hasPubKey {
|
|
err = fmt.Errorf("Missing publicKey parameter")
|
|
}
|
|
|
|
if err == nil {
|
|
deviceId = deviceIDSlice[0]
|
|
token = auth.AuthTokenString(tokenSlice[0])
|
|
pubKey = auth.PublicKey(pubKeySlice[0])
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (s *Server) getWalletState(w http.ResponseWriter, req *http.Request) {
|
|
if !getGetData(w, req) {
|
|
return
|
|
}
|
|
|
|
pubKey, deviceId, token, err := getWalletStateParams(req)
|
|
|
|
if err != nil {
|
|
// In this specific case, err is limited to values that are safe to give to
|
|
// the user
|
|
errorJSON(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
if !s.checkAuth(w, pubKey, deviceId, token, auth.ScopeGetWalletState) {
|
|
return
|
|
}
|
|
|
|
latestWalletStateJSON, latestSignature, err := s.store.GetWalletState(pubKey)
|
|
|
|
var response []byte
|
|
|
|
if err == store.ErrNoWalletState {
|
|
errorJSON(w, http.StatusNotFound, "No wallet state")
|
|
return
|
|
} else if err != nil {
|
|
internalServiceErrorJSON(w, err, "Error retrieving walletState")
|
|
return
|
|
}
|
|
|
|
walletStateResponse := WalletStateResponse{
|
|
BodyJSON: latestWalletStateJSON,
|
|
Signature: latestSignature,
|
|
}
|
|
response, err = json.Marshal(walletStateResponse)
|
|
|
|
if err != nil {
|
|
internalServiceErrorJSON(w, err, "Error generating latestWalletState response")
|
|
return
|
|
}
|
|
|
|
fmt.Fprintf(w, string(response))
|
|
}
|
|
|
|
func (s *Server) postWalletState(w http.ResponseWriter, req *http.Request) {
|
|
var walletStateRequest WalletStateRequest
|
|
if !getPostData(w, req, &walletStateRequest) {
|
|
return
|
|
}
|
|
|
|
if !s.auth.IsValidSignature(walletStateRequest.PubKey, walletStateRequest.BodyJSON, walletStateRequest.Signature) {
|
|
errorJSON(w, http.StatusBadRequest, "Bad signature")
|
|
return
|
|
}
|
|
|
|
var walletState wallet.WalletState
|
|
if err := json.Unmarshal([]byte(walletStateRequest.BodyJSON), &walletState); err != nil {
|
|
errorJSON(w, http.StatusBadRequest, "Malformed walletState JSON")
|
|
return
|
|
}
|
|
|
|
if s.walletUtil.ValidateWalletState(&walletState) {
|
|
// TODO
|
|
}
|
|
|
|
if !s.checkAuth(
|
|
w,
|
|
walletStateRequest.PubKey,
|
|
walletState.DeviceID,
|
|
walletStateRequest.Token,
|
|
auth.ScopeFull,
|
|
) {
|
|
return
|
|
}
|
|
|
|
// TODO - We could do an extra check - pull from db, make sure the new
|
|
// walletState doesn't regress lastSynced for any given device.
|
|
// This is primarily the responsibility of the clients, but we may want to
|
|
// trade a db call here for a double-check against bugs in the client.
|
|
// We do already do some validation checks here, but those doesn't require
|
|
// new database calls.
|
|
|
|
latestWalletStateJSON, latestSignature, updated, err := s.store.SetWalletState(
|
|
walletStateRequest.PubKey,
|
|
walletStateRequest.BodyJSON,
|
|
walletState.Sequence(),
|
|
walletStateRequest.Signature,
|
|
walletStateRequest.DownloadKey,
|
|
)
|
|
|
|
var response []byte
|
|
|
|
if err == store.ErrNoWalletState {
|
|
// We failed to update, and when we tried pulling the latest wallet state,
|
|
// there was nothing there. This should only happen if the client sets
|
|
// sequence != 1 for the first walletState, which would be a bug.
|
|
// TODO - figure out better error messages and/or document this
|
|
errorJSON(w, http.StatusConflict, "Bad sequence number (No existing wallet state)")
|
|
return
|
|
} else if err != nil {
|
|
// Something other than sequence error
|
|
internalServiceErrorJSON(w, err, "Error saving walletState")
|
|
return
|
|
}
|
|
|
|
walletStateResponse := WalletStateResponse{
|
|
BodyJSON: latestWalletStateJSON,
|
|
Signature: latestSignature,
|
|
}
|
|
if !updated {
|
|
// TODO - should we even call this an error?
|
|
walletStateResponse.Error = "Bad sequence number"
|
|
}
|
|
response, err = json.Marshal(walletStateResponse)
|
|
|
|
if err != nil {
|
|
internalServiceErrorJSON(w, err, "Error generating walletState response")
|
|
return
|
|
}
|
|
|
|
// Response Code:
|
|
// 200: Update successful
|
|
// 409: Update unsuccessful, probably due to new walletState's
|
|
// sequence not being 1 + current walletState's sequence
|
|
//
|
|
// Response Body:
|
|
// Current walletState (if it exists). If update successful, we just return
|
|
// the same one passed in. If update not successful, return the latest one
|
|
// from the db for the client to merge.
|
|
if updated {
|
|
fmt.Fprintf(w, string(response))
|
|
} else {
|
|
http.Error(w, string(response), http.StatusConflict)
|
|
}
|
|
}
|