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