237 lines
6.4 KiB
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
|
|
}
|