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