211 lines
4.4 KiB
Go
211 lines
4.4 KiB
Go
package client
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"time"
|
|
|
|
jwt "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"
|
|
)
|
|
|
|
var (
|
|
ErrInvalidPrivateKey = errors.New("invalid private key")
|
|
ErrUnexpectedResponse = errors.New("unexpected response")
|
|
ErrUnauthorized = errors.New("unauthorized")
|
|
ErrRejected = errors.New("rejected")
|
|
ErrInvalidServerToken = errors.New("invalid server token")
|
|
)
|
|
|
|
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{
|
|
Attributes: attrs,
|
|
PublicKey: publicKey,
|
|
}
|
|
|
|
res, err := c.Post(url, data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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.addClientToken(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.addClientToken(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) addServerToken(r *http.Request) {
|
|
r.Header.Set(
|
|
server.ServerTokenHeader,
|
|
c.options.ServerToken,
|
|
)
|
|
}
|
|
|
|
func (c *Client) addClientToken(r *http.Request, body []byte) error {
|
|
bodySum, err := c.createBodySum(body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodRS256, peering.ClientTokenClaims{
|
|
StandardClaims: jwt.StandardClaims{
|
|
NotBefore: time.Now().Add(time.Minute * -1).Unix(),
|
|
ExpiresAt: time.Now().Add(time.Minute * 5).Unix(),
|
|
},
|
|
BodySum: bodySum,
|
|
})
|
|
|
|
tokenStr, err := token.SignedString(c.options.PrivateKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
r.Header.Set(server.ClientTokenHeader, tokenStr)
|
|
|
|
c.addServerToken(r)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) createBodySum(body []byte) ([]byte, error) {
|
|
if body == nil || len(body) == 0 {
|
|
body = []byte("")
|
|
}
|
|
sha := sha256.New()
|
|
_, err := sha.Write(body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sha.Sum(nil), nil
|
|
}
|
|
|
|
func (c *Client) PeerID(serverPublicKey *rsa.PublicKey) (peering.PeerID, error) {
|
|
token, err := jwt.ParseWithClaims(
|
|
c.options.ServerToken,
|
|
&peering.ServerTokenClaims{},
|
|
func(token *jwt.Token) (interface{}, error) {
|
|
return serverPublicKey, nil
|
|
},
|
|
)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !token.Valid {
|
|
return "", ErrInvalidServerToken
|
|
}
|
|
serverClaims, ok := token.Claims.(*peering.ServerTokenClaims)
|
|
if !ok {
|
|
return "", ErrInvalidServerToken
|
|
}
|
|
return serverClaims.PeerID, nil
|
|
}
|
|
|
|
func New(funcs ...OptionFunc) *Client {
|
|
options := createOptions(funcs...)
|
|
return &Client{options}
|
|
}
|