auth.go raw

   1  package auroradns
   2  
   3  import (
   4  	"crypto/hmac"
   5  	"crypto/sha256"
   6  	"encoding/base64"
   7  	"errors"
   8  	"fmt"
   9  	"net/http"
  10  	"time"
  11  )
  12  
  13  // TokenTransport HTTP transport for API authentication.
  14  type TokenTransport struct {
  15  	apiKey string
  16  	secret string
  17  
  18  	// Transport is the underlying HTTP transport to use when making requests.
  19  	// It will default to http.DefaultTransport if nil.
  20  	Transport http.RoundTripper
  21  }
  22  
  23  // NewTokenTransport Creates a  new TokenTransport.
  24  func NewTokenTransport(apiKey, secret string) (*TokenTransport, error) {
  25  	if apiKey == "" || secret == "" {
  26  		return nil, errors.New("credentials missing")
  27  	}
  28  
  29  	return &TokenTransport{apiKey: apiKey, secret: secret}, nil
  30  }
  31  
  32  // RoundTrip executes a single HTTP transaction.
  33  func (t *TokenTransport) RoundTrip(req *http.Request) (*http.Response, error) {
  34  	enrichedReq := &http.Request{}
  35  	*enrichedReq = *req
  36  
  37  	enrichedReq.Header = make(http.Header, len(req.Header))
  38  	for k, s := range req.Header {
  39  		enrichedReq.Header[k] = append([]string(nil), s...)
  40  	}
  41  
  42  	if t.apiKey != "" && t.secret != "" {
  43  		timestamp := time.Now().UTC()
  44  
  45  		fmtTime := timestamp.Format("20060102T150405Z")
  46  		enrichedReq.Header.Set("X-AuroraDNS-Date", fmtTime)
  47  
  48  		token, err := newToken(t.apiKey, t.secret, req.Method, req.URL.Path, timestamp)
  49  		if err == nil {
  50  			enrichedReq.Header.Set("Authorization", fmt.Sprintf("AuroraDNSv1 %s", token))
  51  		}
  52  	}
  53  
  54  	return t.transport().RoundTrip(enrichedReq)
  55  }
  56  
  57  // Wrap Wraps an HTTP client Transport with the TokenTransport.
  58  func (t *TokenTransport) Wrap(client *http.Client) *http.Client {
  59  	backup := client.Transport
  60  	t.Transport = backup
  61  	client.Transport = t
  62  
  63  	return client
  64  }
  65  
  66  // Client Creates a new HTTP client.
  67  func (t *TokenTransport) Client() *http.Client {
  68  	return &http.Client{
  69  		Transport: t,
  70  		Timeout:   30 * time.Second,
  71  	}
  72  }
  73  
  74  func (t *TokenTransport) transport() http.RoundTripper {
  75  	if t.Transport != nil {
  76  		return t.Transport
  77  	}
  78  
  79  	return http.DefaultTransport
  80  }
  81  
  82  // newToken generates a token for accessing a specific method of the API.
  83  func newToken(apiKey, secret, method, action string, timestamp time.Time) (string, error) {
  84  	fmtTime := timestamp.Format("20060102T150405Z")
  85  	message := method + action + fmtTime
  86  
  87  	signatureHmac := hmac.New(sha256.New, []byte(secret))
  88  	_, err := signatureHmac.Write([]byte(message))
  89  	if err != nil {
  90  		return "", err
  91  	}
  92  
  93  	signature := base64.StdEncoding.EncodeToString(signatureHmac.Sum(nil))
  94  
  95  	apiKeyAndSignature := fmt.Sprintf("%s:%s", apiKey, signature)
  96  
  97  	token := base64.StdEncoding.EncodeToString([]byte(apiKeyAndSignature))
  98  
  99  	return token, nil
 100  }
 101