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