go-http-peering/server/handler.go

237 lines
6.4 KiB
Go

package server
import (
"crypto/rsa"
"encoding/json"
"net/http"
"time"
peering "forge.cadoles.com/Cadoles/go-http-peering"
peeringCrypto "forge.cadoles.com/Cadoles/go-http-peering/crypto"
"github.com/pkg/errors"
)
var (
ErrInvalidAdvertisingRequest = errors.New("invalid advertising request")
ErrInvalidUpdateRequest = errors.New("invalid update request")
ErrPeerRejected = errors.New("peer rejected")
ErrPeerIDAlreadyInUse = errors.New("peer id already in use")
ErrUnauthorized = errors.New("unauthorized")
)
func AdvertiseHandler(store peering.Store, key *rsa.PublicKey, funcs ...OptionFunc) http.HandlerFunc {
options := createOptions(funcs...)
logger := options.Logger
handler := func(w http.ResponseWriter, r *http.Request) {
serverToken := r.Header.Get(ServerTokenHeader)
if serverToken == "" {
options.ErrorHandler(w, r, ErrInvalidAdvertisingRequest)
return
}
serverClaims, err := assertServerToken(key, serverToken)
if err != nil {
logger.Printf("[ERROR] %+v", errors.Wrapf(err, "could not validate token '%s'", serverToken))
sendError(w, http.StatusUnauthorized)
return
}
advertising := &peering.AdvertisingRequest{}
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(advertising); err != nil {
logger.Printf("[ERROR] %+v", errors.WithStack(err))
options.ErrorHandler(w, r, ErrInvalidAdvertisingRequest)
return
}
if _, err := peeringCrypto.DecodePEMToPublicKey(advertising.PublicKey); err != nil {
logger.Printf("[ERROR] %+v", errors.WithStack(err))
options.ErrorHandler(w, r, ErrInvalidAdvertisingRequest)
return
}
if _, err := store.Get(serverClaims.PeerID); err == nil {
logger.Printf("[WARN] %s", errors.WithStack(ErrPeerIDAlreadyInUse))
options.ErrorHandler(w, r, ErrPeerIDAlreadyInUse)
return
}
if err != peering.ErrPeerNotFound {
logger.Printf("[ERROR] %+v", errors.WithStack(err))
options.ErrorHandler(w, r, err)
return
}
attrs := filterAttributes(options.PeerAttributes, advertising.Attributes)
peer, err := store.Create(serverClaims.PeerID, attrs)
if err != nil {
logger.Printf("[ERROR] %+v", errors.WithStack(err))
options.ErrorHandler(w, r, err)
return
}
if err := store.UpdateLastContact(peer.ID, r.RemoteAddr, time.Now()); err != nil {
logger.Printf("[ERROR] %+v", errors.WithStack(err))
options.ErrorHandler(w, r, err)
return
}
if err := store.UpdateLastContact(peer.ID, r.RemoteAddr, time.Now()); err != nil {
logger.Printf("[ERROR] %+v", errors.WithStack(err))
options.ErrorHandler(w, r, err)
return
}
if err := store.UpdatePublicKey(peer.ID, advertising.PublicKey); err != nil {
logger.Printf("[ERROR] %+v", errors.WithStack(err))
options.ErrorHandler(w, r, err)
return
}
w.WriteHeader(http.StatusCreated)
}
return handler
}
func UpdateHandler(store peering.Store, funcs ...OptionFunc) http.HandlerFunc {
options := createOptions(funcs...)
logger := options.Logger
handler := func(w http.ResponseWriter, r *http.Request) {
update := &peering.UpdateRequest{}
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(update); err != nil {
options.ErrorHandler(w, r, ErrInvalidUpdateRequest)
return
}
peerID, err := GetPeerID(r)
if err != nil {
logger.Printf("[ERROR] %+v", errors.WithStack(err))
options.ErrorHandler(w, r, err)
return
}
peer, err := store.Get(peerID)
if err != nil {
logger.Printf("[ERROR] %+v", errors.WithStack(err))
options.ErrorHandler(w, r, err)
return
}
if peer == nil {
logger.Printf("[ERROR] %+v", errors.WithStack(ErrUnauthorized))
options.ErrorHandler(w, r, ErrUnauthorized)
return
}
if peer.Status == peering.StatusRejected {
logger.Printf("[ERROR] %+v", errors.WithStack(ErrPeerRejected))
options.ErrorHandler(w, r, ErrPeerRejected)
return
}
if err := store.UpdateLastContact(peer.ID, r.RemoteAddr, time.Now()); err != nil {
logger.Printf("[ERROR] %+v", errors.WithStack(err))
options.ErrorHandler(w, r, err)
return
}
attrs := filterAttributes(options.PeerAttributes, update.Attributes)
if err := store.UpdateAttributes(peer.ID, attrs); err != nil {
logger.Printf("[ERROR] %+v", errors.WithStack(err))
options.ErrorHandler(w, r, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
return handler
}
func PingHandler(store peering.Store, funcs ...OptionFunc) http.HandlerFunc {
options := createOptions(funcs...)
logger := options.Logger
handler := func(w http.ResponseWriter, r *http.Request) {
update := &peering.UpdateRequest{}
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(update); err != nil {
options.ErrorHandler(w, r, ErrInvalidUpdateRequest)
return
}
peerID, err := GetPeerID(r)
if err != nil {
logger.Printf("[ERROR] %+v", errors.WithStack(err))
options.ErrorHandler(w, r, err)
return
}
peer, err := store.Get(peerID)
if err != nil {
logger.Printf("[ERROR] %+v", errors.WithStack(err))
options.ErrorHandler(w, r, err)
return
}
if peer == nil {
logger.Printf("[ERROR] %+v", errors.WithStack(ErrUnauthorized))
options.ErrorHandler(w, r, ErrUnauthorized)
return
}
if peer.Status == peering.StatusRejected {
logger.Printf("[ERROR] %+v", errors.WithStack(ErrPeerRejected))
options.ErrorHandler(w, r, ErrPeerRejected)
return
}
if err := store.UpdateLastContact(peer.ID, r.RemoteAddr, time.Now()); err != nil {
logger.Printf("[ERROR] %+v", errors.WithStack(err))
options.ErrorHandler(w, r, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
return handler
}
func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error) {
switch err {
case ErrInvalidAdvertisingRequest:
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
case ErrPeerIDAlreadyInUse:
http.Error(w, http.StatusText(http.StatusConflict), http.StatusConflict)
case ErrUnauthorized:
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
case ErrPeerRejected:
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
default:
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}
func filterAttributes(filters []string, attrs peering.PeerAttributes) peering.PeerAttributes {
filtered := peering.PeerAttributes{}
for _, key := range filters {
if _, exists := attrs[key]; exists {
filtered[key] = attrs[key]
}
}
return filtered
}