github.com/clerkinc/clerk-sdk-go@v1.49.1/clerk/clerk.go (about)

     1  package clerk
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/url"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  )
    16  
    17  const version = "1.49.0"
    18  
    19  const (
    20  	ProdUrl = "https://api.clerk.dev/v1/"
    21  
    22  	ActorTokensUrl     = "actor_tokens"
    23  	AllowlistsUrl      = "allowlist_identifiers"
    24  	BlocklistsUrl      = "blocklist_identifiers"
    25  	ClientsUrl         = "clients"
    26  	ClientsVerifyUrl   = ClientsUrl + "/verify"
    27  	DomainsURL         = "domains"
    28  	EmailAddressesURL  = "email_addresses"
    29  	EmailsUrl          = "emails"
    30  	InvitationsURL     = "invitations"
    31  	OrganizationsUrl   = "organizations"
    32  	PhoneNumbersURL    = "phone_numbers"
    33  	ProxyChecksURL     = "proxy_checks"
    34  	RedirectURLsUrl    = "redirect_urls"
    35  	SAMLConnectionsUrl = "saml_connections"
    36  	SessionsUrl        = "sessions"
    37  	TemplatesUrl       = "templates"
    38  	UsersUrl           = "users"
    39  	UsersCountUrl      = UsersUrl + "/count"
    40  	WebhooksUrl        = "webhooks"
    41  	JWTTemplatesUrl    = "jwt_templates"
    42  )
    43  
    44  var defaultHTTPClient = &http.Client{Timeout: time.Second * 5}
    45  
    46  type Client interface {
    47  	NewRequest(method, url string, body ...interface{}) (*http.Request, error)
    48  	Do(req *http.Request, v interface{}) (*http.Response, error)
    49  
    50  	DecodeToken(token string) (*TokenClaims, error)
    51  	VerifyToken(token string, opts ...VerifyTokenOption) (*SessionClaims, error)
    52  
    53  	Allowlists() *AllowlistsService
    54  	Blocklists() *BlocklistsService
    55  	Clients() *ClientsService
    56  	Domains() *DomainsService
    57  	EmailAddresses() *EmailAddressesService
    58  	Emails() *EmailService
    59  	ActorTokens() *ActorTokenService
    60  	Instances() *InstanceService
    61  	JWKS() *JWKSService
    62  	JWTTemplates() *JWTTemplatesService
    63  	Organizations() *OrganizationsService
    64  	PhoneNumbers() *PhoneNumbersService
    65  	ProxyChecks() *ProxyChecksService
    66  	RedirectURLs() *RedirectURLsService
    67  	SAMLConnections() *SAMLConnectionsService
    68  	Sessions() *SessionsService
    69  	Templates() *TemplatesService
    70  	Users() *UsersService
    71  	Webhooks() *WebhooksService
    72  	Verification() *VerificationService
    73  	Interstitial() ([]byte, error)
    74  
    75  	APIKey() string
    76  }
    77  
    78  type service struct {
    79  	client Client
    80  }
    81  
    82  type client struct {
    83  	client    *http.Client
    84  	baseURL   *url.URL
    85  	jwksCache *jwksCache
    86  	token     string
    87  
    88  	allowlists      *AllowlistsService
    89  	blocklists      *BlocklistsService
    90  	clients         *ClientsService
    91  	domains         *DomainsService
    92  	emailAddresses  *EmailAddressesService
    93  	emails          *EmailService
    94  	actorTokens     *ActorTokenService
    95  	instances       *InstanceService
    96  	jwks            *JWKSService
    97  	jwtTemplates    *JWTTemplatesService
    98  	organizations   *OrganizationsService
    99  	phoneNumbers    *PhoneNumbersService
   100  	proxyChecks     *ProxyChecksService
   101  	redirectURLs    *RedirectURLsService
   102  	samlConnections *SAMLConnectionsService
   103  	sessions        *SessionsService
   104  	templates       *TemplatesService
   105  	users           *UsersService
   106  	webhooks        *WebhooksService
   107  	verification    *VerificationService
   108  }
   109  
   110  // NewClient creates a new Clerk client.
   111  // Because the token supplied will be used for all authenticated requests,
   112  // the created client should not be used across different users
   113  func NewClient(token string, options ...ClerkOption) (Client, error) {
   114  	if token == "" {
   115  		return nil, errors.New("you must provide an API token")
   116  	}
   117  
   118  	defaultBaseURL, err := toURLWithEndingSlash(ProdUrl)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	client := &client{
   124  		client:  defaultHTTPClient,
   125  		baseURL: defaultBaseURL,
   126  		token:   token,
   127  	}
   128  
   129  	for _, option := range options {
   130  		if err = option(client); err != nil {
   131  			return nil, err
   132  		}
   133  	}
   134  
   135  	commonService := &service{client: client}
   136  	client.allowlists = (*AllowlistsService)(commonService)
   137  	client.blocklists = (*BlocklistsService)(commonService)
   138  	client.clients = (*ClientsService)(commonService)
   139  	client.domains = (*DomainsService)(commonService)
   140  	client.emailAddresses = (*EmailAddressesService)(commonService)
   141  	client.emails = (*EmailService)(commonService)
   142  	client.actorTokens = (*ActorTokenService)(commonService)
   143  	client.instances = (*InstanceService)(commonService)
   144  	client.jwks = (*JWKSService)(commonService)
   145  	client.jwtTemplates = (*JWTTemplatesService)(commonService)
   146  	client.organizations = (*OrganizationsService)(commonService)
   147  	client.phoneNumbers = (*PhoneNumbersService)(commonService)
   148  	client.proxyChecks = (*ProxyChecksService)(commonService)
   149  	client.redirectURLs = (*RedirectURLsService)(commonService)
   150  	client.samlConnections = (*SAMLConnectionsService)(commonService)
   151  	client.sessions = (*SessionsService)(commonService)
   152  	client.templates = (*TemplatesService)(commonService)
   153  	client.users = (*UsersService)(commonService)
   154  	client.webhooks = (*WebhooksService)(commonService)
   155  	client.verification = (*VerificationService)(commonService)
   156  
   157  	client.jwksCache = &jwksCache{}
   158  
   159  	return client, nil
   160  }
   161  
   162  // Deprecated: NewClientWithBaseUrl is deprecated. Use the NewClient instead e.g. NewClient(token, WithBaseURL(baseUrl))
   163  func NewClientWithBaseUrl(token, baseUrl string) (Client, error) {
   164  	return NewClient(token, WithBaseURL(baseUrl))
   165  }
   166  
   167  // Deprecated: NewClientWithCustomHTTP is deprecated. Use the NewClient instead e.g. NewClient(token, WithBaseURL(urlStr), WithHTTPClient(httpClient))
   168  func NewClientWithCustomHTTP(token, urlStr string, httpClient *http.Client) (Client, error) {
   169  	return NewClient(token, WithBaseURL(urlStr), WithHTTPClient(httpClient))
   170  }
   171  
   172  func toURLWithEndingSlash(u string) (*url.URL, error) {
   173  	baseURL, err := url.Parse(u)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  
   178  	if !strings.HasSuffix(baseURL.Path, "/") {
   179  		baseURL.Path += "/"
   180  	}
   181  
   182  	return baseURL, err
   183  }
   184  
   185  // NewRequest creates an API request.
   186  // A relative URL `url` can be specified which is resolved relative to the baseURL of the client.
   187  // Relative URLs should be specified without a preceding slash.
   188  // The `body` parameter can be used to pass a body to the request. If no body is required, the parameter can be omitted.
   189  func (c *client) NewRequest(method, url string, body ...interface{}) (*http.Request, error) {
   190  	fullUrl, err := c.baseURL.Parse(url)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	var buf io.ReadWriter
   196  	if len(body) > 0 && body[0] != nil {
   197  		buf = &bytes.Buffer{}
   198  		enc := json.NewEncoder(buf)
   199  		enc.SetEscapeHTML(false)
   200  		err := enc.Encode(body[0])
   201  		if err != nil {
   202  			return nil, err
   203  		}
   204  	}
   205  
   206  	req, err := http.NewRequest(method, fullUrl.String(), buf)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  
   211  	// Add custom header with the current SDK version
   212  	req.Header.Set("X-Clerk-SDK", fmt.Sprintf("go/%s", version))
   213  
   214  	return req, nil
   215  }
   216  
   217  // Do will send the given request using the client `c` on which it is called.
   218  // If the response contains a body, it will be unmarshalled in `v`.
   219  func (c *client) Do(req *http.Request, v interface{}) (*http.Response, error) {
   220  	req.Header.Set("Authorization", "Bearer "+c.token)
   221  
   222  	resp, err := c.client.Do(req)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  	defer resp.Body.Close()
   227  
   228  	err = checkForErrors(resp)
   229  	if err != nil {
   230  		return resp, err
   231  	}
   232  
   233  	if resp.Body != nil && v != nil {
   234  		body, err := ioutil.ReadAll(resp.Body)
   235  		if err != nil {
   236  			return resp, err
   237  		}
   238  
   239  		err = json.Unmarshal(body, &v)
   240  		if err != nil {
   241  			return resp, err
   242  		}
   243  	}
   244  
   245  	return resp, nil
   246  }
   247  
   248  func checkForErrors(resp *http.Response) error {
   249  	if c := resp.StatusCode; c >= 200 && c < 400 {
   250  		return nil
   251  	}
   252  
   253  	errorResponse := &ErrorResponse{Response: resp}
   254  
   255  	data, err := ioutil.ReadAll(resp.Body)
   256  	if err == nil && data != nil {
   257  		// it's ok if we cannot unmarshal to Clerk's error response
   258  		_ = json.Unmarshal(data, errorResponse)
   259  	}
   260  
   261  	return errorResponse
   262  }
   263  
   264  func (c *client) Allowlists() *AllowlistsService {
   265  	return c.allowlists
   266  }
   267  
   268  func (c *client) Blocklists() *BlocklistsService {
   269  	return c.blocklists
   270  }
   271  
   272  func (c *client) Clients() *ClientsService {
   273  	return c.clients
   274  }
   275  
   276  func (c *client) Domains() *DomainsService {
   277  	return c.domains
   278  }
   279  
   280  func (c *client) EmailAddresses() *EmailAddressesService {
   281  	return c.emailAddresses
   282  }
   283  
   284  func (c *client) Emails() *EmailService {
   285  	return c.emails
   286  }
   287  
   288  func (c *client) ActorTokens() *ActorTokenService {
   289  	return c.actorTokens
   290  }
   291  
   292  func (c *client) Instances() *InstanceService {
   293  	return c.instances
   294  }
   295  
   296  func (c *client) JWKS() *JWKSService {
   297  	return c.jwks
   298  }
   299  
   300  func (c *client) JWTTemplates() *JWTTemplatesService {
   301  	return c.jwtTemplates
   302  }
   303  
   304  func (c *client) Organizations() *OrganizationsService {
   305  	return c.organizations
   306  }
   307  
   308  func (c *client) PhoneNumbers() *PhoneNumbersService {
   309  	return c.phoneNumbers
   310  }
   311  
   312  func (c *client) ProxyChecks() *ProxyChecksService {
   313  	return c.proxyChecks
   314  }
   315  
   316  func (c *client) RedirectURLs() *RedirectURLsService {
   317  	return c.redirectURLs
   318  }
   319  
   320  func (c *client) SAMLConnections() *SAMLConnectionsService {
   321  	return c.samlConnections
   322  }
   323  
   324  func (c *client) Sessions() *SessionsService {
   325  	return c.sessions
   326  }
   327  
   328  func (c *client) Templates() *TemplatesService {
   329  	return c.templates
   330  }
   331  
   332  func (c *client) Users() *UsersService {
   333  	return c.users
   334  }
   335  
   336  func (c *client) Webhooks() *WebhooksService {
   337  	return c.webhooks
   338  }
   339  
   340  func (c *client) Verification() *VerificationService {
   341  	return c.verification
   342  }
   343  
   344  func (c *client) APIKey() string {
   345  	return c.token
   346  }
   347  
   348  func (c *client) Interstitial() ([]byte, error) {
   349  	req, err := c.NewRequest("GET", "internal/interstitial")
   350  	if err != nil {
   351  		return nil, err
   352  	}
   353  	req.Header.Set("Authorization", "Bearer "+c.token)
   354  
   355  	resp, err := c.client.Do(req)
   356  	if err != nil {
   357  		return nil, err
   358  	}
   359  	defer resp.Body.Close()
   360  
   361  	interstitial, err := ioutil.ReadAll(resp.Body)
   362  	if err != nil {
   363  		return interstitial, err
   364  	}
   365  
   366  	return interstitial, nil
   367  }
   368  
   369  type PaginationParams struct {
   370  	Limit  *int
   371  	Offset *int
   372  }
   373  
   374  func addPaginationParams(query url.Values, params PaginationParams) {
   375  	if params.Limit != nil {
   376  		query.Set("limit", strconv.Itoa(*params.Limit))
   377  	}
   378  	if params.Offset != nil {
   379  		query.Set("offset", strconv.Itoa(*params.Offset))
   380  	}
   381  }