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