identity.go raw
1 package internal
2
3 import (
4 "context"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "io"
9 "net/http"
10 "net/url"
11 "strconv"
12 "time"
13
14 "github.com/go-acme/lego/v4/providers/dns/internal/errutils"
15 "github.com/go-acme/lego/v4/providers/dns/internal/useragent"
16 "github.com/pquerna/otp/totp"
17 )
18
19 type token string
20
21 const tokenKey token = "token"
22
23 type Identifier struct {
24 username string
25 password string
26 Secret string
27
28 BaseURL *url.URL
29 HTTPClient *http.Client
30 }
31
32 func NewIdentifier(username, password, secret string) (*Identifier, error) {
33 if username == "" || password == "" {
34 return nil, errors.New("credentials missing")
35 }
36
37 baseURL, _ := url.Parse(defaultBaseURL)
38
39 return &Identifier{
40 username: username,
41 password: password,
42 Secret: secret,
43 BaseURL: baseURL,
44 HTTPClient: &http.Client{Timeout: 10 * time.Second},
45 }, nil
46 }
47
48 func (c *Identifier) Authenticate(ctx context.Context) (*Token, error) {
49 endpoint := c.BaseURL.JoinPath("authenticate")
50
51 auth := Auth{Username: c.username, Password: c.password}
52
53 if c.Secret != "" {
54 tan, err := totp.GenerateCode(c.Secret, time.Now())
55 if err != nil {
56 return nil, fmt.Errorf("generate TOTP: %w", err)
57 }
58
59 auth.Code, err = strconv.Atoi(tan)
60 if err != nil {
61 return nil, fmt.Errorf("parse TOTP: %w", err)
62 }
63 }
64
65 req, err := newJSONRequest(ctx, http.MethodPost, endpoint, auth)
66 if err != nil {
67 return nil, err
68 }
69
70 var result APIResponse[*Token]
71
72 err = c.do(req, &result)
73 if err != nil {
74 return nil, err
75 }
76
77 return result.Data, nil
78 }
79
80 func (c *Identifier) do(req *http.Request, result any) error {
81 useragent.SetHeader(req.Header)
82
83 resp, err := c.HTTPClient.Do(req)
84 if err != nil {
85 return errutils.NewHTTPDoError(req, err)
86 }
87
88 defer func() { _ = resp.Body.Close() }()
89
90 if resp.StatusCode/100 != 2 {
91 return parseError(req, resp)
92 }
93
94 if result == nil {
95 return nil
96 }
97
98 raw, err := io.ReadAll(resp.Body)
99 if err != nil {
100 return errutils.NewReadResponseError(req, resp.StatusCode, err)
101 }
102
103 err = json.Unmarshal(raw, result)
104 if err != nil {
105 return errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
106 }
107
108 return nil
109 }
110
111 func WithContext(ctx context.Context, credential string) context.Context {
112 return context.WithValue(ctx, tokenKey, credential)
113 }
114
115 func getToken(ctx context.Context) string {
116 credential, ok := ctx.Value(tokenKey).(string)
117 if !ok {
118 return ""
119 }
120
121 return credential
122 }
123