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
168 lines
4.4 KiB
Go
168 lines
4.4 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"orblivion/lbry-id/auth"
|
|
"orblivion/lbry-id/store"
|
|
"orblivion/lbry-id/wallet"
|
|
)
|
|
|
|
// TODO proper doc comments!
|
|
|
|
const PathAuthTokenFull = "/auth/full"
|
|
const PathAuthTokenGetWalletState = "/auth/get-wallet-state"
|
|
const PathRegister = "/signup"
|
|
const PathWalletState = "/wallet-state"
|
|
|
|
type Server struct {
|
|
auth auth.AuthInterface
|
|
store store.StoreInterface
|
|
walletUtil wallet.WalletUtilInterface
|
|
}
|
|
|
|
func Init(
|
|
auth auth.AuthInterface,
|
|
store store.StoreInterface,
|
|
walletUtil wallet.WalletUtilInterface,
|
|
) *Server {
|
|
return &Server{
|
|
auth: auth,
|
|
store: store,
|
|
walletUtil: walletUtil,
|
|
}
|
|
}
|
|
|
|
type ErrorResponse struct {
|
|
Error string `json:"error"`
|
|
}
|
|
|
|
func errorJSON(w http.ResponseWriter, code int, extra string) {
|
|
errorStr := http.StatusText(code)
|
|
if extra != "" {
|
|
errorStr = errorStr + ": " + extra
|
|
}
|
|
authErrorJSON, err := json.Marshal(ErrorResponse{Error: errorStr})
|
|
if err != nil {
|
|
// In case something really stupid happens
|
|
http.Error(w, `{"error": "error when JSON-encoding error message"}`, code)
|
|
}
|
|
http.Error(w, string(authErrorJSON), code)
|
|
return
|
|
}
|
|
|
|
// Don't report any details to the user. Log it instead.
|
|
func internalServiceErrorJSON(w http.ResponseWriter, err error, errContext string) {
|
|
errorStr := http.StatusText(http.StatusInternalServerError)
|
|
authErrorJSON, err := json.Marshal(ErrorResponse{Error: errorStr})
|
|
if err != nil {
|
|
// In case something really stupid happens
|
|
http.Error(w, `{"error": "error when JSON-encoding error message"}`, http.StatusInternalServerError)
|
|
}
|
|
http.Error(w, string(authErrorJSON), http.StatusInternalServerError)
|
|
log.Printf("%s: %+v\n", errContext, err)
|
|
|
|
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.
|
|
// TODO the names `getPostData` and `getGetData` don't fully describe what they do
|
|
|
|
func requestOverhead(w http.ResponseWriter, req *http.Request, method string) bool {
|
|
if req.Method != method {
|
|
errorJSON(w, http.StatusMethodNotAllowed, "")
|
|
return false
|
|
}
|
|
|
|
/*
|
|
TODO - http.StatusRequestEntityTooLarge for some arbitrary large size
|
|
see:
|
|
* MaxBytesReader or LimitReader
|
|
* https://pkg.go.dev/net/http#Request.ParseForm
|
|
* some library/framework that handles it (along with req.Method)
|
|
|
|
also - GET params too large?
|
|
*/
|
|
|
|
return true
|
|
}
|
|
|
|
// All structs representing incoming json request body should implement this
|
|
type PostRequest interface {
|
|
validate() bool
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
if err := json.NewDecoder(req.Body).Decode(&reqStruct); err != nil {
|
|
errorJSON(w, http.StatusBadRequest, "Malformed request body JSON")
|
|
return false
|
|
}
|
|
|
|
if !reqStruct.validate() {
|
|
// TODO validate() should return useful error messages instead of a bool.
|
|
errorJSON(w, http.StatusBadRequest, "Request failed validation")
|
|
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)
|
|
}
|
|
|
|
func (s *Server) checkAuth(
|
|
w http.ResponseWriter,
|
|
pubKey auth.PublicKey,
|
|
deviceId string,
|
|
token auth.AuthTokenString,
|
|
scope auth.AuthScope,
|
|
) bool {
|
|
authToken, err := s.store.GetToken(pubKey, deviceId)
|
|
if err == store.ErrNoToken {
|
|
errorJSON(w, http.StatusUnauthorized, "Token Not Found")
|
|
return false
|
|
}
|
|
if err != nil {
|
|
internalServiceErrorJSON(w, err, "Error getting Token")
|
|
return false
|
|
}
|
|
|
|
if authToken.Token != token {
|
|
errorJSON(w, http.StatusUnauthorized, "Token Invalid")
|
|
return false
|
|
}
|
|
|
|
if !authToken.ScopeValid(scope) {
|
|
errorJSON(w, http.StatusForbidden, "Scope")
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// 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."
|
|
|
|
func (s *Server) Serve() {
|
|
http.HandleFunc(PathAuthTokenGetWalletState, s.getAuthTokenForGetWalletState)
|
|
http.HandleFunc(PathAuthTokenFull, s.getAuthTokenFull)
|
|
http.HandleFunc(PathWalletState, s.handleWalletState)
|
|
http.HandleFunc(PathRegister, s.register)
|
|
|
|
fmt.Println("Serving at :8090")
|
|
http.ListenAndServe(":8090", nil)
|
|
}
|