package client import ( "bytes" "crypto/sha256" "encoding/json" "errors" "fmt" "net/http" "time" "github.com/dgrijalva/jwt-go" peering "forge.cadoles.com/wpetit/go-http-peering" "forge.cadoles.com/wpetit/go-http-peering/crypto" "forge.cadoles.com/wpetit/go-http-peering/server" ) const ( DefaultBody = "{}" ) var ( ErrInvalidPrivateKey = errors.New("invalid private key") ErrUnexpectedResponse = errors.New("unexpected response") ErrUnauthorized = errors.New("unauthorized") ErrRejected = errors.New("rejected") ) type Client struct { options *Options } func (c *Client) Advertise(attrs peering.PeerAttributes) error { url := c.options.BaseURL + peering.AdvertisePath publicKey, err := crypto.EncodePublicKeyToPEM(c.options.PrivateKey.Public()) if err != nil { return err } data := &peering.AdvertisingRequest{ ID: c.options.PeerID, Attributes: attrs, PublicKey: publicKey, } req, _, err := c.createPostRequest(url, data) if err != nil { return err } res, err := c.options.HTTPClient.Do(req) switch res.StatusCode { case http.StatusCreated: return nil case http.StatusConflict: return peering.ErrPeerExists default: return ErrUnexpectedResponse } } func (c *Client) UpdateAttributes(attrs peering.PeerAttributes) error { url := c.options.BaseURL + peering.UpdatePath data := &peering.UpdateRequest{ Attributes: attrs, } res, err := c.Post(url, data) if err != nil { return err } switch res.StatusCode { case http.StatusNoContent: return nil case http.StatusUnauthorized: return ErrUnauthorized case http.StatusForbidden: return ErrRejected default: return ErrUnexpectedResponse } } func (c *Client) Ping() error { url := c.options.BaseURL + peering.PingPath res, err := c.Post(url, nil) if err != nil { return err } switch res.StatusCode { case http.StatusNoContent: return nil case http.StatusUnauthorized: return ErrUnauthorized case http.StatusForbidden: return ErrRejected default: return ErrUnexpectedResponse } } func (c *Client) Get(url string) (*http.Response, error) { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } if err := c.signRequest(req, nil); err != nil { return nil, err } return c.options.HTTPClient.Do(req) } func (c *Client) Post(url string, data interface{}) (*http.Response, error) { req, body, err := c.createPostRequest(url, data) if err != nil { return nil, err } if err := c.signRequest(req, body); err != nil { return nil, err } return c.options.HTTPClient.Do(req) } func (c *Client) createPostRequest(url string, data interface{}) (*http.Request, []byte, error) { body, err := json.Marshal(data) if err != nil { return nil, nil, err } req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body)) if err != nil { return nil, nil, err } req.Header.Set("Content-Type", "application/json") return req, body, nil } func (c *Client) signRequest(r *http.Request, body []byte) error { bodySum, err := c.createBodySum(body) if err != nil { return err } token := jwt.NewWithClaims(jwt.SigningMethodRS256, peering.PeerClaims{ StandardClaims: jwt.StandardClaims{ NotBefore: time.Now().Unix(), Issuer: string(c.options.PeerID), ExpiresAt: time.Now().Add(time.Minute * 10).Unix(), }, BodySum: bodySum, }) tokenStr, err := token.SignedString(c.options.PrivateKey) if err != nil { return err } r.Header.Set("Authorization", fmt.Sprintf("%s %s", server.AuthorizationType, tokenStr)) return nil } func (c *Client) createBodySum(body []byte) ([]byte, error) { if body == nil || len(body) == 0 { body = []byte(DefaultBody) } sha := sha256.New() _, err := sha.Write(body) if err != nil { return nil, err } return sha.Sum(nil), nil } func New(funcs ...OptionFunc) *Client { options := createOptions(funcs...) return &Client{options} }