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 }