2021-12-25 02:16:58 +01:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2022-08-27 17:37:09 +02:00
|
|
|
"context"
|
2021-12-25 02:16:58 +01:00
|
|
|
"encoding/json"
|
2022-08-01 17:50:16 +02:00
|
|
|
"fmt"
|
2021-12-25 02:16:58 +01:00
|
|
|
"log"
|
|
|
|
"net/http"
|
2022-08-27 17:37:09 +02:00
|
|
|
"os"
|
|
|
|
"os/signal"
|
2022-08-25 22:35:00 +02:00
|
|
|
"strings"
|
2022-07-13 18:32:48 +02:00
|
|
|
|
2022-07-23 01:49:30 +02:00
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
|
|
|
2022-08-22 18:05:53 +02:00
|
|
|
"lbryio/wallet-sync-server/auth"
|
|
|
|
"lbryio/wallet-sync-server/env"
|
|
|
|
"lbryio/wallet-sync-server/mail"
|
|
|
|
"lbryio/wallet-sync-server/server/paths"
|
|
|
|
"lbryio/wallet-sync-server/store"
|
2022-08-27 17:37:09 +02:00
|
|
|
"lbryio/wallet-sync-server/wallet"
|
2021-12-25 02:16:58 +01:00
|
|
|
)
|
|
|
|
|
2022-08-22 23:44:26 +02:00
|
|
|
const maxBodySize = 100000
|
|
|
|
|
2022-08-27 17:37:09 +02:00
|
|
|
// Message sent from the wallet POST request handler to the websocket manager,
|
|
|
|
// indicating that a user's client should receive a (different) message that
|
|
|
|
// their wallet has an update on the server.
|
|
|
|
type walletUpdateMsg struct {
|
|
|
|
userId auth.UserId
|
|
|
|
sequence wallet.Sequence
|
|
|
|
}
|
|
|
|
|
2021-12-25 02:16:58 +01:00
|
|
|
type Server struct {
|
2022-06-09 23:04:49 +02:00
|
|
|
auth auth.AuthInterface
|
|
|
|
store store.StoreInterface
|
2022-07-24 00:13:56 +02:00
|
|
|
env env.EnvInterface
|
2022-07-28 01:45:09 +02:00
|
|
|
mail mail.MailInterface
|
2022-08-01 17:50:16 +02:00
|
|
|
port int
|
2022-08-27 17:37:09 +02:00
|
|
|
|
|
|
|
clientAdd chan wsClientForUser
|
|
|
|
clientRemove chan wsClientForUser
|
|
|
|
userRemove chan wsClientForUser
|
|
|
|
walletUpdates chan walletUpdateMsg
|
2021-12-25 02:16:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func Init(
|
2022-08-27 17:37:09 +02:00
|
|
|
authInterface auth.AuthInterface,
|
|
|
|
storeInterface store.StoreInterface,
|
|
|
|
envInterface env.EnvInterface,
|
|
|
|
mailInterface mail.MailInterface,
|
2022-08-01 17:50:16 +02:00
|
|
|
port int,
|
2021-12-25 02:16:58 +01:00
|
|
|
) *Server {
|
2022-08-27 17:37:09 +02:00
|
|
|
return &Server{
|
|
|
|
auth: authInterface,
|
|
|
|
store: storeInterface,
|
|
|
|
env: envInterface,
|
|
|
|
mail: mailInterface,
|
|
|
|
port: port,
|
|
|
|
|
|
|
|
// Anything that could get backed up by a lot of requests, let's just
|
|
|
|
// give it a buffer. Starting small until we start to see dashboard
|
|
|
|
// stats on this. I want a sense of how this grows with the number of
|
|
|
|
// users or whatnot.
|
|
|
|
clientAdd: make(chan wsClientForUser),
|
|
|
|
clientRemove: make(chan wsClientForUser),
|
|
|
|
userRemove: make(chan wsClientForUser, 5),
|
|
|
|
walletUpdates: make(chan walletUpdateMsg, 5),
|
|
|
|
}
|
2021-12-25 02:16:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type ErrorResponse struct {
|
|
|
|
Error string `json:"error"`
|
|
|
|
}
|
|
|
|
|
2022-06-07 19:25:14 +02:00
|
|
|
func errorJson(w http.ResponseWriter, code int, extra string) {
|
2021-12-25 02:16:58 +01:00
|
|
|
errorStr := http.StatusText(code)
|
|
|
|
if extra != "" {
|
|
|
|
errorStr = errorStr + ": " + extra
|
|
|
|
}
|
2022-06-07 19:25:14 +02:00
|
|
|
authErrorJson, err := json.Marshal(ErrorResponse{Error: errorStr})
|
2021-12-25 02:16:58 +01:00
|
|
|
if err != nil {
|
|
|
|
// In case something really stupid happens
|
|
|
|
http.Error(w, `{"error": "error when JSON-encoding error message"}`, code)
|
|
|
|
}
|
2022-06-07 19:25:14 +02:00
|
|
|
http.Error(w, string(authErrorJson), code)
|
2021-12-25 02:16:58 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't report any details to the user. Log it instead.
|
2022-06-07 19:25:14 +02:00
|
|
|
func internalServiceErrorJson(w http.ResponseWriter, serverErr error, errContext string) {
|
2021-12-25 02:16:58 +01:00
|
|
|
errorStr := http.StatusText(http.StatusInternalServerError)
|
2022-06-07 19:25:14 +02:00
|
|
|
authErrorJson, err := json.Marshal(ErrorResponse{Error: errorStr})
|
2021-12-25 02:16:58 +01:00
|
|
|
if err != nil {
|
|
|
|
// In case something really stupid happens
|
|
|
|
http.Error(w, `{"error": "error when JSON-encoding error message"}`, http.StatusInternalServerError)
|
2022-06-07 19:25:14 +02:00
|
|
|
log.Printf("error when JSON-encoding error message")
|
|
|
|
return
|
2021-12-25 02:16:58 +01:00
|
|
|
}
|
2022-06-07 19:25:14 +02:00
|
|
|
http.Error(w, string(authErrorJson), http.StatusInternalServerError)
|
|
|
|
log.Printf("%s: %+v\n", errContext, serverErr)
|
2021-12-25 02:16:58 +01:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////
|
|
|
|
// Handler Helpers
|
|
|
|
//////////////////
|
|
|
|
|
|
|
|
// Cut down on code repetition. No need to return errors since it can all be
|
|
|
|
// handled here. Just return a bool to indicate success.
|
|
|
|
|
|
|
|
func requestOverhead(w http.ResponseWriter, req *http.Request, method string) bool {
|
|
|
|
if req.Method != method {
|
2022-06-07 19:25:14 +02:00
|
|
|
errorJson(w, http.StatusMethodNotAllowed, "")
|
2021-12-25 02:16:58 +01:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// All structs representing incoming json request body should implement this
|
2022-07-11 15:42:08 +02:00
|
|
|
// The contents of `error` should be safe for an API response (public-facing)
|
2021-12-25 02:16:58 +01:00
|
|
|
type PostRequest interface {
|
2022-07-11 15:42:08 +02:00
|
|
|
validate() error
|
2021-12-25 02:16:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Confirm it's a Post request, various overhead, decode the json, validate the struct
|
|
|
|
func getPostData(w http.ResponseWriter, req *http.Request, reqStruct PostRequest) bool {
|
|
|
|
if !requestOverhead(w, req, http.MethodPost) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-06-19 23:49:05 +02:00
|
|
|
// Make the limit 100k. Increase from there as needed. I'd rather block some
|
|
|
|
// people's large wallets and increase the limit than OOM for everybody and
|
|
|
|
// decrease the limit.
|
2022-08-22 23:44:26 +02:00
|
|
|
req.Body = http.MaxBytesReader(w, req.Body, maxBodySize)
|
2022-08-25 22:35:00 +02:00
|
|
|
decoder := json.NewDecoder(req.Body)
|
|
|
|
decoder.DisallowUnknownFields()
|
|
|
|
err := decoder.Decode(&reqStruct)
|
2022-06-19 23:49:05 +02:00
|
|
|
switch {
|
|
|
|
case err == nil:
|
|
|
|
break
|
|
|
|
case err.Error() == "http: request body too large":
|
|
|
|
errorJson(w, http.StatusRequestEntityTooLarge, "")
|
|
|
|
return false
|
2022-08-25 22:35:00 +02:00
|
|
|
case strings.HasPrefix(err.Error(), "json: unknown field"):
|
|
|
|
// The error is coming straight out of the json decoder. I think the prefix
|
|
|
|
// we check for determines what it is pretty reliably. I'd think it's safe
|
|
|
|
// to give back to the requesting client (unlike an arbitrary error
|
|
|
|
// message).
|
|
|
|
errorJson(w, http.StatusBadRequest, err.Error())
|
|
|
|
return false
|
2022-06-19 23:49:05 +02:00
|
|
|
default:
|
2022-08-25 22:35:00 +02:00
|
|
|
// Maybe we can suss out more specific errors later. Need to study what
|
|
|
|
// errors come from Decode.
|
2022-06-19 23:49:05 +02:00
|
|
|
errorJson(w, http.StatusBadRequest, "Error parsing JSON")
|
2021-12-25 02:16:58 +01:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-07-11 15:42:08 +02:00
|
|
|
err = reqStruct.validate()
|
|
|
|
if err != nil {
|
|
|
|
errorJson(w, http.StatusBadRequest, "Request failed validation: "+err.Error())
|
2021-12-25 02:16:58 +01:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Confirm it's a Get request, various overhead
|
|
|
|
func getGetData(w http.ResponseWriter, req *http.Request) bool {
|
|
|
|
return requestOverhead(w, req, http.MethodGet)
|
|
|
|
}
|
|
|
|
|
2022-06-07 19:25:14 +02:00
|
|
|
// TODO - probably don't return all of authToken since we only need userId and
|
2022-08-25 21:55:02 +02:00
|
|
|
// deviceId.
|
2021-12-25 02:16:58 +01:00
|
|
|
func (s *Server) checkAuth(
|
|
|
|
w http.ResponseWriter,
|
2022-07-28 01:45:09 +02:00
|
|
|
token auth.AuthTokenString,
|
2021-12-25 02:16:58 +01:00
|
|
|
scope auth.AuthScope,
|
2022-06-07 19:25:14 +02:00
|
|
|
) *auth.AuthToken {
|
|
|
|
authToken, err := s.store.GetToken(token)
|
2022-07-26 22:36:57 +02:00
|
|
|
if err == store.ErrNoTokenForUserDevice {
|
2022-06-07 19:25:14 +02:00
|
|
|
errorJson(w, http.StatusUnauthorized, "Token Not Found")
|
|
|
|
return nil
|
2021-12-25 02:16:58 +01:00
|
|
|
}
|
|
|
|
if err != nil {
|
2022-06-07 19:25:14 +02:00
|
|
|
internalServiceErrorJson(w, err, "Error getting Token")
|
|
|
|
return nil
|
2021-12-25 02:16:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if !authToken.ScopeValid(scope) {
|
2022-06-07 19:25:14 +02:00
|
|
|
errorJson(w, http.StatusForbidden, "Scope")
|
|
|
|
return nil
|
2021-12-25 02:16:58 +01:00
|
|
|
}
|
|
|
|
|
2022-06-07 19:25:14 +02:00
|
|
|
return authToken
|
2021-12-25 02:16:58 +01:00
|
|
|
}
|
|
|
|
|
2022-08-27 17:37:09 +02:00
|
|
|
// Useful for any request where token is the only GET param to get
|
|
|
|
// TODO - There's probably a struct-based solution here like with POST/PUT.
|
|
|
|
func getTokenParam(req *http.Request) (token auth.AuthTokenString, err error) {
|
|
|
|
tokenSlice, hasTokenSlice := req.URL.Query()["token"]
|
|
|
|
|
|
|
|
if !hasTokenSlice || tokenSlice[0] == "" {
|
|
|
|
err = fmt.Errorf("Missing token parameter")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
token = auth.AuthTokenString(tokenSlice[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-25 02:16:58 +01:00
|
|
|
// TODO - both wallet and token requests should be PUT, not POST.
|
|
|
|
// PUT = "...creates a new resource or replaces a representation of the target resource with the request payload."
|
|
|
|
|
2022-07-21 21:14:21 +02:00
|
|
|
func (s *Server) unknownEndpoint(w http.ResponseWriter, req *http.Request) {
|
|
|
|
errorJson(w, http.StatusNotFound, "Unknown Endpoint")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) wrongApiVersion(w http.ResponseWriter, req *http.Request) {
|
2022-08-01 17:50:16 +02:00
|
|
|
errorJson(w, http.StatusNotFound, "Wrong API version. Current version is "+paths.ApiVersion+".")
|
2022-07-21 21:14:21 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-08-27 17:37:09 +02:00
|
|
|
func serve(server *http.Server, done chan bool) {
|
|
|
|
log.Print("Server start")
|
|
|
|
server.ListenAndServe()
|
|
|
|
log.Print("Server finish")
|
|
|
|
|
|
|
|
done <- true
|
|
|
|
}
|
|
|
|
|
2021-12-25 02:16:58 +01:00
|
|
|
func (s *Server) Serve() {
|
2022-08-01 17:50:16 +02:00
|
|
|
http.HandleFunc(paths.PathAuthToken, s.getAuthToken)
|
|
|
|
http.HandleFunc(paths.PathWallet, s.handleWallet)
|
|
|
|
http.HandleFunc(paths.PathRegister, s.register)
|
|
|
|
http.HandleFunc(paths.PathPassword, s.changePassword)
|
|
|
|
http.HandleFunc(paths.PathVerify, s.verify)
|
|
|
|
http.HandleFunc(paths.PathResendVerify, s.resendVerifyEmail)
|
|
|
|
http.HandleFunc(paths.PathClientSaltSeed, s.getClientSaltSeed)
|
2022-08-27 17:37:09 +02:00
|
|
|
http.HandleFunc(paths.PathWebsocket, s.websocket)
|
2021-12-25 02:16:58 +01:00
|
|
|
|
2022-08-01 17:50:16 +02:00
|
|
|
http.HandleFunc(paths.PathUnknownEndpoint, s.unknownEndpoint)
|
|
|
|
http.HandleFunc(paths.PathWrongApiVersion, s.wrongApiVersion)
|
2022-07-21 21:14:21 +02:00
|
|
|
|
2022-08-01 17:50:16 +02:00
|
|
|
http.Handle(paths.PathPrometheus, promhttp.Handler())
|
2022-07-23 01:49:30 +02:00
|
|
|
|
2022-08-01 17:50:16 +02:00
|
|
|
log.Printf("Serving at localhost:%d\n", s.port)
|
2022-08-27 17:37:09 +02:00
|
|
|
|
|
|
|
// Signal *to* socket manager that it should finish (we use server.Shutdown
|
|
|
|
// to tell the server to finish)
|
|
|
|
socketsFinish := make(chan bool)
|
|
|
|
|
|
|
|
// Signal *from* server and socket manager that they are done:
|
|
|
|
serverDone := make(chan bool)
|
|
|
|
socketsDone := make(chan bool)
|
|
|
|
|
|
|
|
go s.manageSockets(socketsDone, socketsFinish)
|
|
|
|
|
|
|
|
server := http.Server{Addr: fmt.Sprintf("localhost:%d", s.port)}
|
|
|
|
go serve(&server, serverDone)
|
|
|
|
|
|
|
|
// Make sure that both the server and the websocket manager close properly on interrupt
|
|
|
|
interrupt := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(interrupt, os.Interrupt)
|
|
|
|
|
|
|
|
// Wait for the interrupt signal
|
|
|
|
<-interrupt
|
|
|
|
|
|
|
|
// Tell the server to finish and wait for it to do so. We want it to finish
|
|
|
|
// to guarantee no more incoming sockets before we turn off the socket
|
|
|
|
// manager.
|
|
|
|
server.Shutdown(context.Background())
|
|
|
|
<-serverDone
|
|
|
|
|
|
|
|
// The socket manager's cleanup procedure assumes that there will be no new
|
|
|
|
// socket connections. Now that the server is done, no new socket
|
|
|
|
// connections will be coming in, so we can close the socket manager.
|
|
|
|
socketsFinish <- true
|
|
|
|
<-socketsDone
|
|
|
|
|
|
|
|
log.Printf("All done")
|
2021-12-25 02:16:58 +01:00
|
|
|
}
|