identity.go raw
1 package internal
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "net/http"
9 "net/url"
10 "strings"
11 "time"
12
13 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
14 )
15
16 type token string
17
18 const tokenKey token = "token"
19
20 // obtainToken Logs into cloud.ru and acquires a bearer token for use in future API calls.
21 // https://cloud.ru/ru/docs/clouddns/ug/topics/api-ref_authentication.html
22 func (c *Client) obtainToken(ctx context.Context) (*Token, error) {
23 data := make(url.Values)
24 data.Set("grant_type", "access_key")
25 data.Set("client_id", c.keyID)
26 data.Set("client_secret", c.secret)
27
28 req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.AuthEndpoint.String(), strings.NewReader(data.Encode()))
29 if err != nil {
30 return nil, fmt.Errorf("unable to create request: %w", err)
31 }
32
33 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
34
35 resp, err := c.HTTPClient.Do(req)
36 if err != nil {
37 return nil, errutils.NewHTTPDoError(req, err)
38 }
39
40 defer func() { _ = resp.Body.Close() }()
41
42 if resp.StatusCode != http.StatusOK {
43 return nil, parseError(req, resp)
44 }
45
46 raw, err := io.ReadAll(resp.Body)
47 if err != nil {
48 return nil, errutils.NewReadResponseError(req, resp.StatusCode, err)
49 }
50
51 tok := Token{}
52
53 err = json.Unmarshal(raw, &tok)
54 if err != nil {
55 return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
56 }
57
58 if !strings.EqualFold(tok.TokenType, "Bearer") {
59 return nil, fmt.Errorf("received unexpected token type: %s", tok.TokenType)
60 }
61
62 tok.Deadline = time.Now().Add(time.Duration(tok.ExpiresIn) * time.Second)
63
64 return &tok, nil
65 }
66
67 func (c *Client) CreateAuthenticatedContext(ctx context.Context) (context.Context, error) {
68 c.muToken.Lock()
69 defer c.muToken.Unlock()
70
71 if c.token != nil && time.Now().Before(c.token.Deadline) {
72 // Already authenticated, stop now
73 return context.WithValue(ctx, tokenKey, c.token), nil
74 }
75
76 tok, err := c.obtainToken(ctx)
77 if err != nil {
78 return nil, err
79 }
80
81 return context.WithValue(ctx, tokenKey, tok), nil
82 }
83
84 func parseError(req *http.Request, resp *http.Response) error {
85 if resp.StatusCode < 400 || resp.StatusCode > 499 {
86 return errutils.NewUnexpectedResponseStatusCodeError(req, resp)
87 }
88
89 raw, _ := io.ReadAll(resp.Body)
90
91 errResp := &authResponseError{}
92
93 err := json.Unmarshal(raw, errResp)
94 if err != nil {
95 return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
96 }
97
98 return fmt.Errorf("%d: %w", resp.StatusCode, errResp)
99 }
100
101 func getToken(ctx context.Context) *Token {
102 tok, ok := ctx.Value(tokenKey).(*Token)
103 if !ok {
104 return nil
105 }
106
107 return tok
108 }
109