diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..4b087cd1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI + +on: + pull_request: + push: + branches: + - $default-branch + - v2 + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup go + uses: actions/setup-go@v4 + with: + go-version: "1.21" + - name: Format + run: diff -u <(echo -n) <(gofmt -d -s .) + - name: Vet + run: go vet ./... + - name: Run linter + uses: golangci/golangci-lint-action@v3 + test: + name: "Test: go v${{ matrix.go-version }}" + strategy: + matrix: + go-version: + - "1.19" + - "1.20" + - "1.21" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup go + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + - name: Run tests + run: go test -v -race ./... diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 52640963..00000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,55 +0,0 @@ -on: - pull_request: - push: - branches: [$default-branch] - -name: tests -env: - GO111MODULE: on - -jobs: - test: - strategy: - matrix: - go-version: [1.16.x] - platform: [ubuntu-latest] - runs-on: ${{ matrix.platform }} - - steps: - - uses: actions/setup-go@v4 - with: - go-version: ${{ matrix.go-version }} - - uses: actions/checkout@v3 - - - name: Cache go modules - uses: actions/cache@v3 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }} - restore-keys: ${{ runner.os }}-go- - - - name: Run go fmt - if: runner.os != 'Windows' - run: diff -u <(echo -n) <(gofmt -d -s .) - - - name: Run go vet - run: go vet ./... - - - name: Run go test - run: go test -v -race -coverprofile coverage.out -covermode atomic ./... - - - name: Run coverage - if: runner.os != 'Windows' - run: | - EXPECTED_COVER=95 - TOTAL_COVER=`go tool cover -func=coverage.out | grep total | grep -Eo '[0-9]+\.[0-9]+'` - echo "Total coverage was: $TOTAL_COVER %" - echo "Expected coverage: $EXPECTED_COVER %" - - - name: Run integration tests - env: - CLERK_API_URL: ${{ secrets.CLERK_API_URL }} - CLERK_API_KEY: ${{ secrets.CLERK_API_KEY }} - CLERK_SESSION_TOKEN: ${{ secrets.CLERK_SESSION_TOKEN }} - CLERK_SESSION_ID: ${{ secrets.CLERK_SESSION_ID }} - run: go test -tags=integration ./tests/integration diff --git a/README.md b/README.md index fc557a8f..b101b544 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,20 @@ # Clerk Go SDK -Go client library for accessing the [Clerk Backend API](https://clerk.com/docs/reference/backend-api). +**This is still a work in progress. The current stable release is v1. See the main branch for the stable release.** -[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/clerkinc/clerk-sdk-go/clerk) -[![Test Status](https://github.com/clerkinc/clerk-sdk-go/workflows/tests/badge.svg)](https://github.com/clerkinc/clerk-sdk-go/actions?query=workflow%3Atests) +The official Go client library for accessing the [Clerk Backend API](https://clerk.com/docs/reference/backend-api). + +[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/clerk/clerk-sdk-go/v2) [![chat on Discord](https://img.shields.io/discord/856971667393609759.svg?logo=discord)](https://discord.com/invite/b5rXHjAg7A) [![documentation](https://img.shields.io/badge/documentation-clerk-green.svg)](https://clerk.com/docs) [![twitter](https://img.shields.io/twitter/follow/ClerkDev?style=social)](https://twitter.com/intent/follow?screen_name=ClerkDev) +## License + +This SDK is licensed under the MIT license found in the [LICENSE](./LICENSE) file. + --- **Clerk is Hiring!** @@ -26,102 +31,3 @@ Go client library for accessing the [Clerk Backend API](https://clerk.com/docs/r Would you like to work on Open Source software and help maintain this repository? [Apply today!](https://apply.workable.com/clerk-dev/) --- - -## Usage - -First, add the Clerk SDK as a dependency to your project. - -``` -$ go get github.com/clerkinc/clerk-sdk-go -``` - -Add the following import to your Go files. - -```go -import "github.com/clerkinc/clerk-sdk-go/clerk" -``` - -Now, you can create a Clerk client by calling the `clerk.NewClient` function. -This function requires your Clerk API key. -You can get this from the dashboard of your Clerk application. - -Once you have a client, you can use the various services to access different parts of the API. - -```go -apiKey := os.Getenv("CLERK_API_KEY") - -client, err := clerk.NewClient(apiKey) -if err != nil { - // handle error -} - -// List all users for current application -users, err := client.Users().ListAll(clerk.ListAllUsersParams{}) -``` - -The services exposed in the `clerk.Client` divide the API into logical chunks and -follow the same structure that can be found in the [Backend API documentation](https://clerk.com/docs/reference/backend-api). - -For more examples on how to use the client, refer to the [examples](https://github.com/clerkinc/clerk-sdk-go/tree/main/examples/operations) - -### Options - -The SDK `Client` constructor can also accept additional options defined [here](https://github.com/clerk/clerk-sdk-go/blob/main/clerk/clerk_options.go). - -A common use case is injecting your own [`http.Client` object](https://pkg.go.dev/net/http#Client) for testing or automatically retrying requests. -An example using [go-retryablehttp](https://github.com/hashicorp/go-retryablehttp/#getting-a-stdlib-httpclient-with-retries) is shown below: - -```go -retryClient := retryablehttp.NewClient() -retryClient.RetryMax = 5 -standardClient := retryClient.StandardClient() // *http.Client - -clerkSDKClient := clerk.NewClient(token, clerk.WithHTTPClient(standardClient)) -``` - -## Middleware - -The SDK provides the [`WithSessionV2`](https://pkg.go.dev/github.com/clerkinc/clerk-sdk-go/v2/clerk#WithSessionV2) middleware that injects the active session into the request's context. - -The active session's claims can then be accessed using [`SessionFromContext`](https://pkg.go.dev/github.com/clerkinc/clerk-sdk-go/v2/clerk#SessionFromContext). - -```go -mux := http.NewServeMux() -injectActiveSession := clerk.WithSessionV2(client) -mux.Handle("/your-endpoint", injectActiveSession(yourEndpointHandler)) -``` - -Additionally, there's [`RequireSessionV2`](https://pkg.go.dev/github.com/clerkinc/clerk-sdk-go/v2/clerk#RequireSessionV2) that will halt the request and respond with 403 if the user is not authenticated. This can be used to restrict access to certain routes unless the user is authenticated. - -For more info on how to use the middleware, refer to the -[example](https://github.com/clerkinc/clerk-sdk-go/tree/main/examples/middleware). - -### Additional options - -The middleware supports the following options: - -- clerk.WithAuthorizedParty() to set the authorized parties to check against the azp claim of the token -- clerk.WithLeeway() to set a custom leeway that gives some extra time to the token to accommodate for clock skew -- clerk.WithJWTVerificationKey() to set the JWK to use for verifying tokens without the need to fetch or cache any JWKs at runtime -- clerk.WithCustomClaims() to pass a type (e.g. struct), which will be populated with the token claims based on json tags. -- clerk.WithSatelliteDomain() to skip the JWT token's "iss" claim verification. -- clerk.WithProxyURL() to verify the JWT token's "iss" claim against the proxy url. - -For example - -```golang -customClaims := myCustomClaimsStruct{} - -clerk.WithSessionV2( - clerkClient, - clerk.WithAuthorizedParty("my-authorized-party"), - clerk.WithLeeway(5 * time.Second), - clerk.WithCustomClaims(&customClaims), - clerk.WithSatelliteDomain(true), - clerk.WithProxyURL("https://example.com/__clerk"), - ) -``` - -## License - -This SDK is licensed under the MIT license found in the [LICENSE](./LICENSE) file. diff --git a/clerk.go b/clerk.go new file mode 100644 index 00000000..a6f4b2fa --- /dev/null +++ b/clerk.go @@ -0,0 +1,368 @@ +// Package clerk provides a way to communicate with the Clerk API. +// Includes types for Clerk API requests, responses and all +// available resources. +package clerk + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "sync" + "time" +) + +const ( + sdkVersion string = "v2.0.0" + clerkAPIVersion string = "v1" +) + +const ( + // APIURL is the base URL for the Clerk API. + APIURL string = "https://api.clerk.com" +) + +// The Clerk secret key. Configured on a package level. +var secretKey string + +// SetKey sets the Clerk API key. +func SetKey(key string) { + secretKey = key +} + +// APIResource describes a Clerk API resource and contains fields and +// methods common to all resources. +type APIResource struct { + Response *APIResponse `json:"-"` +} + +// Read sets the response on the resource. +func (r *APIResource) Read(response *APIResponse) { + r.Response = response +} + +// APIParams implements functionality that's common to all types +// that can be used as API request parameters. +// It is recommended to embed this type to all types that will be +// used for API operation parameters. +type APIParams struct { +} + +// Add can be used to set parameters to url.Values. The method +// is currently a no-op, but is defined so that all types that +// describe API operation parameters implement the Queryable +// interface. +func (params *APIParams) Add(q url.Values) { +} + +// APIResponse describes responses coming from the Clerk API. +// Exposes some commonly used HTTP response fields along with +// the raw data in the response body. +type APIResponse struct { + Header http.Header + Status string // e.g. "200 OK" + StatusCode int // e.g. 200 + + // TraceID is a unique identifier for tracing the origin of the + // response. + // Useful for debugging purposes. + TraceID string + // RawJSON contains the response body as raw bytes. + RawJSON json.RawMessage +} + +// Success returns true for API response status codes in the +// 200-399 range, false otherwise. +func (resp *APIResponse) Success() bool { + return resp.StatusCode < 400 +} + +// NewAPIResponse creates an APIResponse from the passed http.Response +// and the raw response body. +func NewAPIResponse(resp *http.Response, body json.RawMessage) *APIResponse { + return &APIResponse{ + Header: resp.Header, + TraceID: resp.Header.Get("Clerk-Trace-Id"), + Status: resp.Status, + StatusCode: resp.StatusCode, + RawJSON: body, + } +} + +// APIRequest describes requests to the Clerk API. +type APIRequest struct { + Method string + Path string + Params Queryable +} + +// SetParams sets the APIRequest.Params. +func (req *APIRequest) SetParams(params Queryable) { + req.Params = params +} + +// NewAPIRequest creates an APIRequest with the provided HTTP method +// and path. +func NewAPIRequest(method, path string) *APIRequest { + return &APIRequest{ + Method: method, + Path: path, + } +} + +// Backend is the primary interface for communicating with the Clerk +// API. +type Backend interface { + // Call makes requests to the Clerk API. + Call(context.Context, *APIRequest, ResponseReader) error +} + +// ResponseReader reads Clerk API responses. +type ResponseReader interface { + Read(*APIResponse) +} + +// Queryable can add parameters to url.Values. +// Useful for constructing a request query string. +type Queryable interface { + Add(url.Values) +} + +// BackendConfig is used to configure a new Clerk Backend. +type BackendConfig struct { + // HTTPClient is an HTTP client instance that will be used for + // making API requests. + // If it's not set a default HTTP client will be used. + HTTPClient *http.Client + // URL is the base URL to use for API endpoints. + // If it's not set, the default value for the Backend will be used. + URL *string +} + +// NewBackend returns a default backend implementation with the +// provided configuration. +// Please note that the return type is an interface because the +// Backend is not supposed to be used directly. +func NewBackend(config *BackendConfig) Backend { + if config.HTTPClient == nil { + config.HTTPClient = defaultHTTPClient + } + if config.URL == nil { + config.URL = String(APIURL) + } + return &defaultBackend{ + HTTPClient: config.HTTPClient, + URL: *config.URL, + } +} + +// GetBackend returns the library's supported backend for the Clerk +// API. +func GetBackend() Backend { + var b Backend + + backend.mu.RLock() + b = backend.Backend + backend.mu.RUnlock() + + if b != nil { + return b + } + + b = NewBackend(&BackendConfig{}) + SetBackend(b) + return b +} + +// SetBackend sets the Backend that will be used to make requests +// to the Clerk API. +// Use this method if you need to override the default Backend +// configuration. +func SetBackend(b Backend) { + backend.mu.Lock() + defer backend.mu.Unlock() + backend.Backend = b +} + +type defaultBackend struct { + HTTPClient *http.Client + URL string +} + +// Call sends requests to the Clerk API and handles the responses. +func (b *defaultBackend) Call(ctx context.Context, apiReq *APIRequest, setter ResponseReader) error { + req, err := b.newRequest(ctx, apiReq) + if err != nil { + return err + } + + return b.do(req, apiReq.Params, setter) +} + +func (b *defaultBackend) newRequest(ctx context.Context, apiReq *APIRequest) (*http.Request, error) { + path, err := url.JoinPath(b.URL, clerkAPIVersion, apiReq.Path) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, apiReq.Method, path, nil) + if err != nil { + return nil, err + } + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", secretKey)) + req.Header.Add("Content-Type", "application/json") + req.Header.Add("User-Agent", fmt.Sprintf("Clerk/%s SDK-Go/%s", clerkAPIVersion, sdkVersion)) + req.Header.Add("X-Clerk-SDK", fmt.Sprintf("go/%s", sdkVersion)) + req = req.WithContext(ctx) + + return req, nil +} + +func (b *defaultBackend) do(req *http.Request, params Queryable, setter ResponseReader) error { + err := setRequestBody(req, params) + if err != nil { + return err + } + + resp, err := b.HTTPClient.Do(req) + if err != nil { + return err + } + resBody, err := io.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return err + } + + apiResponse := NewAPIResponse(resp, resBody) + // Looks like something went wrong. Handle the error. + if !apiResponse.Success() { + return handleError(apiResponse, resBody) + } + + setter.Read(apiResponse) + err = json.Unmarshal(resBody, setter) + if err != nil { + return err + } + + return nil +} + +// Sets the params in either the request body, or the querystring +// for GET requests. +func setRequestBody(req *http.Request, params Queryable) error { + // GET requests don't have a body, but we will pass the params + // in the query string. + if req.Method == http.MethodGet { + q := req.URL.Query() + params.Add(q) + req.URL.RawQuery = q.Encode() + return nil + } + + body, err := json.Marshal(params) + if err != nil { + return err + } + req.Body = io.NopCloser(bytes.NewReader(body)) + req.GetBody = func() (io.ReadCloser, error) { + return io.NopCloser(bytes.NewReader(body)), nil + } + + return nil +} + +// Error response handling +func handleError(resp *APIResponse, body []byte) error { + apiError := &APIErrorResponse{ + HTTPStatusCode: resp.StatusCode, + } + apiError.Read(resp) + err := json.Unmarshal(body, apiError) + if err != nil || apiError.Errors == nil { + // This is probably not an expected API error. + // Return the raw server response. + return errors.New(string(body)) + } + return apiError +} + +// The active Backend +var backend api + +// This type is a container for a Backend. Guarantees thread-safe +// access to the current Backend. +type api struct { + Backend Backend + mu sync.RWMutex +} + +// defaultHTTPTimeout is the default timeout on the http.Client used +// by the library. +const defaultHTTPTimeout = 5 * time.Second + +// The default HTTP client used for communication with the Clerk API. +var defaultHTTPClient = &http.Client{ + Timeout: defaultHTTPTimeout, +} + +// APIErrorResponse is used for cases where requests to the Clerk +// API result in error responses. +type APIErrorResponse struct { + APIResource + + Errors []Error `json:"errors"` + + HTTPStatusCode int `json:"status,omitempty"` + TraceID string `json:"clerk_trace_id,omitempty"` +} + +// Error returns the marshaled representation of the APIErrorResponse. +func (resp *APIErrorResponse) Error() string { + ret, err := json.Marshal(resp) + if err != nil { + // This shouldn't happen, let's return the raw response + return string(resp.Response.RawJSON) + } + return string(ret) +} + +// Error is a representation of a single error that can occur in the +// Clerk API. +type Error struct { + Code string `json:"code"` + Message string `json:"message"` + LongMessage string `json:"long_message"` + Meta json.RawMessage `json:"meta,omitempty"` +} + +// ListParams holds fields that are common for list API operations. +type ListParams struct { + Limit *int64 `json:"limit,omitempty"` + Offset *int64 `json:"offset,omitempty"` +} + +// Add sets list params to the passed in url.Values. +func (params ListParams) Add(q url.Values) { + if params.Limit != nil { + q.Set("limit", strconv.FormatInt(*params.Limit, 10)) + } + if params.Offset != nil { + q.Set("offset", strconv.FormatInt(*params.Offset, 10)) + } +} + +// String returns a pointer to the provided string value. +func String(v string) *string { + return &v +} + +// Int64 returns a pointer to the provided int64 value. +func Int64(v int64) *int64 { + return &v +} diff --git a/clerk/actor_tokens.go b/clerk/actor_tokens.go deleted file mode 100644 index 6bb9bfc9..00000000 --- a/clerk/actor_tokens.go +++ /dev/null @@ -1,49 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" -) - -type ActorTokenService service - -type ActorTokenResponse struct { - Object string `json:"object"` - ID string `json:"id"` - UserID string `json:"user_id"` - Actor json.RawMessage `json:"actor"` - Token string `json:"token,omitempty"` - Status string `json:"status"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -type CreateActorTokenParams struct { - UserID string `json:"user_id"` - Actor json.RawMessage `json:"actor"` - ExpiresInSeconds *int `json:"expires_in_seconds"` - SessionMaxDurationInSeconds *int `json:"session_max_duration_in_seconds"` -} - -func (s *ActorTokenService) Create(params CreateActorTokenParams) (*ActorTokenResponse, error) { - req, _ := s.client.NewRequest(http.MethodPost, ActorTokensUrl, ¶ms) - - var actorTokenResponse ActorTokenResponse - _, err := s.client.Do(req, &actorTokenResponse) - if err != nil { - return nil, err - } - return &actorTokenResponse, nil -} - -func (s *ActorTokenService) Revoke(actorTokenID string) (*ActorTokenResponse, error) { - req, _ := s.client.NewRequest(http.MethodPost, fmt.Sprintf("%s/%s/revoke", ActorTokensUrl, actorTokenID)) - - var actorTokenResponse ActorTokenResponse - _, err := s.client.Do(req, &actorTokenResponse) - if err != nil { - return nil, err - } - return &actorTokenResponse, nil -} diff --git a/clerk/actor_tokens_test.go b/clerk/actor_tokens_test.go deleted file mode 100644 index b3f54f94..00000000 --- a/clerk/actor_tokens_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestActorTokenService_CreateIdentifier_happyPath(t *testing.T) { - token := "token" - var actorTokenResponse ActorTokenResponse - _ = json.Unmarshal([]byte(dummyActorTokenJson), &actorTokenResponse) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/actor_tokens", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPost) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyActorTokenJson) - }) - - got, _ := client.ActorTokens().Create(CreateActorTokenParams{ - Actor: actorTokenResponse.Actor, - UserID: actorTokenResponse.UserID, - }) - - assert.Equal(t, &actorTokenResponse, got) -} - -func TestActorTokenService_Create_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.ActorTokens().Create(CreateActorTokenParams{ - Actor: []byte(`{"sub":"some_actor_id"}`), - UserID: "some_user_id", - }) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestActorTokenService_Revoke_happyPath(t *testing.T) { - token := "token" - var actorTokenResponse ActorTokenResponse - _ = json.Unmarshal([]byte(dummyActorTokenJson), &actorTokenResponse) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/actor_tokens/"+actorTokenResponse.ID+"/revoke", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPost) - testHeader(t, req, "Authorization", "Bearer "+token) - _, _ = fmt.Fprint(w, dummyActorTokenJson) - }) - - got, _ := client.ActorTokens().Revoke(actorTokenResponse.ID) - assert.Equal(t, &actorTokenResponse, got) -} - -func TestActorTokenService_Revoke_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.ActorTokens().Revoke("impt_2EKxJqKTYcBMlzMh6BGe2C7kh6b") - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -const dummyActorTokenJson = `{ - "id": "impt_2EKxJqKTYcBMlzMh6BGe2C7kh6b", - "object": "actor_token", - "actor": { - "sub": "some_actor_id", - "iss": "the-issuer" - }, - "user_id": "user_2EKwinD5cZID96QP1ruHsnfGx50", - "status": "pending", - "token": "my-token", - "created_at": 1662358007949, - "updated_at": 1662358007949 -}` diff --git a/clerk/allowlists.go b/clerk/allowlists.go deleted file mode 100644 index 228a614c..00000000 --- a/clerk/allowlists.go +++ /dev/null @@ -1,61 +0,0 @@ -package clerk - -import "net/http" - -type AllowlistsService service - -type AllowlistIdentifierResponse struct { - Object string `json:"object"` - ID string `json:"id"` - InvitationID string `json:"invitation_id,omitempty"` - Identifier string `json:"identifier"` - IdentifierType string `json:"identifier_type"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -type CreateAllowlistIdentifierParams struct { - Identifier string `json:"identifier"` - Notify bool `json:"notify"` -} - -func (s *AllowlistsService) CreateIdentifier(params CreateAllowlistIdentifierParams) (*AllowlistIdentifierResponse, error) { - req, _ := s.client.NewRequest(http.MethodPost, AllowlistsUrl, ¶ms) - - var allowlistIdentifierResponse AllowlistIdentifierResponse - _, err := s.client.Do(req, &allowlistIdentifierResponse) - if err != nil { - return nil, err - } - return &allowlistIdentifierResponse, nil -} - -func (s *AllowlistsService) DeleteIdentifier(identifierID string) (*DeleteResponse, error) { - req, _ := s.client.NewRequest(http.MethodDelete, AllowlistsUrl+"/"+identifierID) - - var deleteResponse DeleteResponse - _, err := s.client.Do(req, &deleteResponse) - if err != nil { - return nil, err - } - return &deleteResponse, nil -} - -type AllowlistIdentifiersResponse struct { - Data []*AllowlistIdentifierResponse `json:"data"` - TotalCount int64 `json:"total_count"` -} - -func (s *AllowlistsService) ListAllIdentifiers() (*AllowlistIdentifiersResponse, error) { - req, _ := s.client.NewRequest(http.MethodGet, AllowlistsUrl) - - var allowlistIdentifiersResponse []*AllowlistIdentifierResponse - _, err := s.client.Do(req, &allowlistIdentifiersResponse) - if err != nil { - return nil, err - } - return &AllowlistIdentifiersResponse{ - Data: allowlistIdentifiersResponse, - TotalCount: int64(len(allowlistIdentifiersResponse)), - }, nil -} diff --git a/clerk/allowlists_test.go b/clerk/allowlists_test.go deleted file mode 100644 index 89b00ddc..00000000 --- a/clerk/allowlists_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAllowlistService_CreateIdentifier_happyPath(t *testing.T) { - token := "token" - var allowlistIdentifier AllowlistIdentifierResponse - _ = json.Unmarshal([]byte(dummyAllowlistIdentifierJson), &allowlistIdentifier) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/allowlist_identifiers", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPost) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyAllowlistIdentifierJson) - }) - - got, _ := client.Allowlists().CreateIdentifier(CreateAllowlistIdentifierParams{ - Identifier: allowlistIdentifier.Identifier, - }) - - assert.Equal(t, &allowlistIdentifier, got) -} - -func TestAllowlistService_CreateIdentifier_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.Allowlists().CreateIdentifier(CreateAllowlistIdentifierParams{ - Identifier: "dummy@example.com", - }) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestAllowlistService_DeleteIdentifier_happyPath(t *testing.T) { - token := "token" - var allowlistIdentifier BlocklistIdentifierResponse - _ = json.Unmarshal([]byte(dummyBlocklistIdentifierJson), &allowlistIdentifier) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/allowlist_identifiers/"+allowlistIdentifier.ID, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodDelete) - testHeader(t, req, "Authorization", "Bearer "+token) - response := fmt.Sprintf(`{ "deleted": true, "id": "%s", "object": "allowlist_identifier" }`, allowlistIdentifier.ID) - _, _ = fmt.Fprint(w, response) - }) - - got, _ := client.Allowlists().DeleteIdentifier(allowlistIdentifier.ID) - assert.Equal(t, allowlistIdentifier.ID, got.ID) - assert.True(t, got.Deleted) -} - -func TestAllowlistService_DeleteIdentifier_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.Allowlists().DeleteIdentifier("alid_1mvFol71HiKCcypBd6xxg0IpMBN") - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestAllowlistService_ListAllIdentifiers_happyPath(t *testing.T) { - token := "token" - var allowlistIdentifier AllowlistIdentifierResponse - _ = json.Unmarshal([]byte(dummyAllowlistIdentifierJson), &allowlistIdentifier) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/allowlist_identifiers", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodGet) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, fmt.Sprintf(`[%s]`, dummyAllowlistIdentifierJson)) - }) - - got, _ := client.Allowlists().ListAllIdentifiers() - - assert.Len(t, got.Data, 1) - assert.Equal(t, int64(1), got.TotalCount) - assert.Equal(t, &allowlistIdentifier, got.Data[0]) -} - -func TestAllowlistService_ListAllIdentifiers_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.Allowlists().ListAllIdentifiers() - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -const dummyAllowlistIdentifierJson = `{ - "id": "alid_1mvFol71HiKCcypBd6xxg0IpMBN", - "object": "allowlist_identifier", - "identifier": "dummy@example.com", - "identifier_type": "email_address", - "invitation_id": "inv_1mvFol71PeRCcypBd628g0IuRmF", - "created_at": 1610783813, - "updated_at": 1610783813 -}` diff --git a/clerk/blocklists.go b/clerk/blocklists.go deleted file mode 100644 index 2b9632b1..00000000 --- a/clerk/blocklists.go +++ /dev/null @@ -1,58 +0,0 @@ -package clerk - -import ( - "net/http" -) - -type BlocklistsService service - -type BlocklistIdentifierResponse struct { - Object string `json:"object"` - ID string `json:"id"` - Identifier string `json:"identifier"` - IdentifierType string `json:"identifier_type"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -type CreateBlocklistIdentifierParams struct { - Identifier string `json:"identifier"` -} - -func (s *BlocklistsService) CreateIdentifier(params CreateBlocklistIdentifierParams) (*BlocklistIdentifierResponse, error) { - req, _ := s.client.NewRequest(http.MethodPost, BlocklistsUrl, ¶ms) - - var blocklistIdentifierResponse BlocklistIdentifierResponse - _, err := s.client.Do(req, &blocklistIdentifierResponse) - if err != nil { - return nil, err - } - return &blocklistIdentifierResponse, nil -} - -func (s *BlocklistsService) DeleteIdentifier(identifierID string) (*DeleteResponse, error) { - req, _ := s.client.NewRequest(http.MethodDelete, BlocklistsUrl+"/"+identifierID) - - var deleteResponse DeleteResponse - _, err := s.client.Do(req, &deleteResponse) - if err != nil { - return nil, err - } - return &deleteResponse, nil -} - -type BlocklistIdentifiersResponse struct { - Data []*BlocklistIdentifierResponse `json:"data"` - TotalCount int64 `json:"total_count"` -} - -func (s *BlocklistsService) ListAllIdentifiers() (*BlocklistIdentifiersResponse, error) { - req, _ := s.client.NewRequest(http.MethodGet, BlocklistsUrl) - - var blocklistIdentifiersResponse *BlocklistIdentifiersResponse - _, err := s.client.Do(req, &blocklistIdentifiersResponse) - if err != nil { - return nil, err - } - return blocklistIdentifiersResponse, nil -} diff --git a/clerk/blocklists_test.go b/clerk/blocklists_test.go deleted file mode 100644 index 3b3f6fbc..00000000 --- a/clerk/blocklists_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestBlocklistService_CreateIdentifier_happyPath(t *testing.T) { - token := "token" - var blocklistIdentifier BlocklistIdentifierResponse - _ = json.Unmarshal([]byte(dummyBlocklistIdentifierJson), &blocklistIdentifier) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/blocklist_identifiers", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPost) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyBlocklistIdentifierJson) - }) - - got, _ := client.Blocklists().CreateIdentifier(CreateBlocklistIdentifierParams{ - Identifier: blocklistIdentifier.Identifier, - }) - - assert.Equal(t, &blocklistIdentifier, got) -} - -func TestBlocklistService_CreateIdentifier_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.Blocklists().CreateIdentifier(CreateBlocklistIdentifierParams{ - Identifier: "dummy@example.com", - }) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestBlocklistService_DeleteIdentifier_happyPath(t *testing.T) { - token := "token" - var blocklistIdentifier BlocklistIdentifierResponse - _ = json.Unmarshal([]byte(dummyBlocklistIdentifierJson), &blocklistIdentifier) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/blocklist_identifiers/"+blocklistIdentifier.ID, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodDelete) - testHeader(t, req, "Authorization", "Bearer "+token) - response := fmt.Sprintf(`{ "deleted": true, "id": "%s", "object": "blocklist_identifier" }`, blocklistIdentifier.ID) - _, _ = fmt.Fprint(w, response) - }) - - got, _ := client.Blocklists().DeleteIdentifier(blocklistIdentifier.ID) - assert.Equal(t, blocklistIdentifier.ID, got.ID) - assert.True(t, got.Deleted) -} - -func TestBlocklistService_DeleteIdentifier_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.Blocklists().DeleteIdentifier("blid_1mvFol71HiKCcypBd6xxg0IpMBN") - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestBlocklistService_ListAllIdentifiers_happyPath(t *testing.T) { - token := "token" - var blocklistIdentifier BlocklistIdentifierResponse - _ = json.Unmarshal([]byte(dummyBlocklistIdentifierJson), &blocklistIdentifier) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/blocklist_identifiers", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodGet) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, fmt.Sprintf(`{"data": [%s], "total_count": 1}`, dummyBlocklistIdentifierJson)) - }) - - got, _ := client.Blocklists().ListAllIdentifiers() - - assert.Len(t, got.Data, 1) - assert.Equal(t, int64(1), got.TotalCount) - assert.Equal(t, &blocklistIdentifier, got.Data[0]) -} - -func TestBlocklistService_ListAllIdentifiers_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.Blocklists().ListAllIdentifiers() - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -const dummyBlocklistIdentifierJson = `{ - "id": "blid_1mvFol71HiKCcypBd6xxg0IpMBN", - "object": "blocklist_identifier", - "identifier": "dummy@example.com", - "identifier_type": "email_address", - "created_at": 1610783813, - "updated_at": 1610783813 -}` diff --git a/clerk/clerk.go b/clerk/clerk.go deleted file mode 100644 index f5d125d2..00000000 --- a/clerk/clerk.go +++ /dev/null @@ -1,381 +0,0 @@ -package clerk - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "strconv" - "strings" - "time" -) - -const version = "1.49.0" - -const ( - ProdUrl = "https://api.clerk.dev/v1/" - - ActorTokensUrl = "actor_tokens" - AllowlistsUrl = "allowlist_identifiers" - BlocklistsUrl = "blocklist_identifiers" - ClientsUrl = "clients" - ClientsVerifyUrl = ClientsUrl + "/verify" - DomainsURL = "domains" - EmailAddressesURL = "email_addresses" - EmailsUrl = "emails" - InvitationsURL = "invitations" - OrganizationsUrl = "organizations" - PhoneNumbersURL = "phone_numbers" - ProxyChecksURL = "proxy_checks" - RedirectURLsUrl = "redirect_urls" - SAMLConnectionsUrl = "saml_connections" - SessionsUrl = "sessions" - TemplatesUrl = "templates" - UsersUrl = "users" - UsersCountUrl = UsersUrl + "/count" - WebhooksUrl = "webhooks" - JWTTemplatesUrl = "jwt_templates" -) - -var defaultHTTPClient = &http.Client{Timeout: time.Second * 5} - -type Client interface { - NewRequest(method, url string, body ...interface{}) (*http.Request, error) - Do(req *http.Request, v interface{}) (*http.Response, error) - - DecodeToken(token string) (*TokenClaims, error) - VerifyToken(token string, opts ...VerifyTokenOption) (*SessionClaims, error) - - Allowlists() *AllowlistsService - Blocklists() *BlocklistsService - Clients() *ClientsService - Domains() *DomainsService - EmailAddresses() *EmailAddressesService - Emails() *EmailService - ActorTokens() *ActorTokenService - Instances() *InstanceService - JWKS() *JWKSService - JWTTemplates() *JWTTemplatesService - Organizations() *OrganizationsService - PhoneNumbers() *PhoneNumbersService - ProxyChecks() *ProxyChecksService - RedirectURLs() *RedirectURLsService - SAMLConnections() *SAMLConnectionsService - Sessions() *SessionsService - Templates() *TemplatesService - Users() *UsersService - Webhooks() *WebhooksService - Verification() *VerificationService - Interstitial() ([]byte, error) - - APIKey() string -} - -type service struct { - client Client -} - -type client struct { - client *http.Client - baseURL *url.URL - jwksCache *jwksCache - token string - - allowlists *AllowlistsService - blocklists *BlocklistsService - clients *ClientsService - domains *DomainsService - emailAddresses *EmailAddressesService - emails *EmailService - actorTokens *ActorTokenService - instances *InstanceService - jwks *JWKSService - jwtTemplates *JWTTemplatesService - organizations *OrganizationsService - phoneNumbers *PhoneNumbersService - proxyChecks *ProxyChecksService - redirectURLs *RedirectURLsService - samlConnections *SAMLConnectionsService - sessions *SessionsService - templates *TemplatesService - users *UsersService - webhooks *WebhooksService - verification *VerificationService -} - -// NewClient creates a new Clerk client. -// Because the token supplied will be used for all authenticated requests, -// the created client should not be used across different users -func NewClient(token string, options ...ClerkOption) (Client, error) { - if token == "" { - return nil, errors.New("you must provide an API token") - } - - defaultBaseURL, err := toURLWithEndingSlash(ProdUrl) - if err != nil { - return nil, err - } - - client := &client{ - client: defaultHTTPClient, - baseURL: defaultBaseURL, - token: token, - } - - for _, option := range options { - if err = option(client); err != nil { - return nil, err - } - } - - commonService := &service{client: client} - client.allowlists = (*AllowlistsService)(commonService) - client.blocklists = (*BlocklistsService)(commonService) - client.clients = (*ClientsService)(commonService) - client.domains = (*DomainsService)(commonService) - client.emailAddresses = (*EmailAddressesService)(commonService) - client.emails = (*EmailService)(commonService) - client.actorTokens = (*ActorTokenService)(commonService) - client.instances = (*InstanceService)(commonService) - client.jwks = (*JWKSService)(commonService) - client.jwtTemplates = (*JWTTemplatesService)(commonService) - client.organizations = (*OrganizationsService)(commonService) - client.phoneNumbers = (*PhoneNumbersService)(commonService) - client.proxyChecks = (*ProxyChecksService)(commonService) - client.redirectURLs = (*RedirectURLsService)(commonService) - client.samlConnections = (*SAMLConnectionsService)(commonService) - client.sessions = (*SessionsService)(commonService) - client.templates = (*TemplatesService)(commonService) - client.users = (*UsersService)(commonService) - client.webhooks = (*WebhooksService)(commonService) - client.verification = (*VerificationService)(commonService) - - client.jwksCache = &jwksCache{} - - return client, nil -} - -// Deprecated: NewClientWithBaseUrl is deprecated. Use the NewClient instead e.g. NewClient(token, WithBaseURL(baseUrl)) -func NewClientWithBaseUrl(token, baseUrl string) (Client, error) { - return NewClient(token, WithBaseURL(baseUrl)) -} - -// Deprecated: NewClientWithCustomHTTP is deprecated. Use the NewClient instead e.g. NewClient(token, WithBaseURL(urlStr), WithHTTPClient(httpClient)) -func NewClientWithCustomHTTP(token, urlStr string, httpClient *http.Client) (Client, error) { - return NewClient(token, WithBaseURL(urlStr), WithHTTPClient(httpClient)) -} - -func toURLWithEndingSlash(u string) (*url.URL, error) { - baseURL, err := url.Parse(u) - if err != nil { - return nil, err - } - - if !strings.HasSuffix(baseURL.Path, "/") { - baseURL.Path += "/" - } - - return baseURL, err -} - -// NewRequest creates an API request. -// A relative URL `url` can be specified which is resolved relative to the baseURL of the client. -// Relative URLs should be specified without a preceding slash. -// The `body` parameter can be used to pass a body to the request. If no body is required, the parameter can be omitted. -func (c *client) NewRequest(method, url string, body ...interface{}) (*http.Request, error) { - fullUrl, err := c.baseURL.Parse(url) - if err != nil { - return nil, err - } - - var buf io.ReadWriter - if len(body) > 0 && body[0] != nil { - buf = &bytes.Buffer{} - enc := json.NewEncoder(buf) - enc.SetEscapeHTML(false) - err := enc.Encode(body[0]) - if err != nil { - return nil, err - } - } - - req, err := http.NewRequest(method, fullUrl.String(), buf) - if err != nil { - return nil, err - } - - // Add custom header with the current SDK version - req.Header.Set("X-Clerk-SDK", fmt.Sprintf("go/%s", version)) - - return req, nil -} - -// Do will send the given request using the client `c` on which it is called. -// If the response contains a body, it will be unmarshalled in `v`. -func (c *client) Do(req *http.Request, v interface{}) (*http.Response, error) { - req.Header.Set("Authorization", "Bearer "+c.token) - - resp, err := c.client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - err = checkForErrors(resp) - if err != nil { - return resp, err - } - - if resp.Body != nil && v != nil { - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return resp, err - } - - err = json.Unmarshal(body, &v) - if err != nil { - return resp, err - } - } - - return resp, nil -} - -func checkForErrors(resp *http.Response) error { - if c := resp.StatusCode; c >= 200 && c < 400 { - return nil - } - - errorResponse := &ErrorResponse{Response: resp} - - data, err := ioutil.ReadAll(resp.Body) - if err == nil && data != nil { - // it's ok if we cannot unmarshal to Clerk's error response - _ = json.Unmarshal(data, errorResponse) - } - - return errorResponse -} - -func (c *client) Allowlists() *AllowlistsService { - return c.allowlists -} - -func (c *client) Blocklists() *BlocklistsService { - return c.blocklists -} - -func (c *client) Clients() *ClientsService { - return c.clients -} - -func (c *client) Domains() *DomainsService { - return c.domains -} - -func (c *client) EmailAddresses() *EmailAddressesService { - return c.emailAddresses -} - -func (c *client) Emails() *EmailService { - return c.emails -} - -func (c *client) ActorTokens() *ActorTokenService { - return c.actorTokens -} - -func (c *client) Instances() *InstanceService { - return c.instances -} - -func (c *client) JWKS() *JWKSService { - return c.jwks -} - -func (c *client) JWTTemplates() *JWTTemplatesService { - return c.jwtTemplates -} - -func (c *client) Organizations() *OrganizationsService { - return c.organizations -} - -func (c *client) PhoneNumbers() *PhoneNumbersService { - return c.phoneNumbers -} - -func (c *client) ProxyChecks() *ProxyChecksService { - return c.proxyChecks -} - -func (c *client) RedirectURLs() *RedirectURLsService { - return c.redirectURLs -} - -func (c *client) SAMLConnections() *SAMLConnectionsService { - return c.samlConnections -} - -func (c *client) Sessions() *SessionsService { - return c.sessions -} - -func (c *client) Templates() *TemplatesService { - return c.templates -} - -func (c *client) Users() *UsersService { - return c.users -} - -func (c *client) Webhooks() *WebhooksService { - return c.webhooks -} - -func (c *client) Verification() *VerificationService { - return c.verification -} - -func (c *client) APIKey() string { - return c.token -} - -func (c *client) Interstitial() ([]byte, error) { - req, err := c.NewRequest("GET", "internal/interstitial") - if err != nil { - return nil, err - } - req.Header.Set("Authorization", "Bearer "+c.token) - - resp, err := c.client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - interstitial, err := ioutil.ReadAll(resp.Body) - if err != nil { - return interstitial, err - } - - return interstitial, nil -} - -type PaginationParams struct { - Limit *int - Offset *int -} - -func addPaginationParams(query url.Values, params PaginationParams) { - if params.Limit != nil { - query.Set("limit", strconv.Itoa(*params.Limit)) - } - if params.Offset != nil { - query.Set("offset", strconv.Itoa(*params.Offset)) - } -} diff --git a/clerk/clerk_options.go b/clerk/clerk_options.go deleted file mode 100644 index 4c9a2d6d..00000000 --- a/clerk/clerk_options.go +++ /dev/null @@ -1,38 +0,0 @@ -package clerk - -import ( - "errors" - "net/http" -) - -// ClerkOption describes a functional parameter for the clerk client constructor -type ClerkOption func(*client) error - -// WithHTTPClient allows the overriding of the http client -func WithHTTPClient(httpClient *http.Client) ClerkOption { - return func(c *client) error { - if httpClient == nil { - return errors.New("http client can't be nil") - } - - c.client = httpClient - return nil - } -} - -// WithBaseURL allows the overriding of the base URL -func WithBaseURL(rawURL string) ClerkOption { - return func(c *client) error { - if rawURL == "" { - return errors.New("base url can't be empty") - } - - baseURL, err := toURLWithEndingSlash(rawURL) - if err != nil { - return err - } - - c.baseURL = baseURL - return nil - } -} diff --git a/clerk/clerk_options_test.go b/clerk/clerk_options_test.go deleted file mode 100644 index 13cb6ede..00000000 --- a/clerk/clerk_options_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package clerk - -import ( - "net/http" - "testing" - "time" -) - -func TestWithHTTPClient(t *testing.T) { - expectedHTTPClient := &http.Client{Timeout: time.Second * 10} - - got, err := NewClient("token", WithHTTPClient(expectedHTTPClient)) - if err != nil { - t.Fatal(err) - } - - if got.(*client).client != expectedHTTPClient { - t.Fatalf("Expected the http client to have been overriden") - } -} - -func TestWithHTTPClientNil(t *testing.T) { - _, err := NewClient("token", WithHTTPClient(nil)) - if err == nil { - t.Fatalf("Expected an error with a nil http client provided") - } -} - -func TestWithBaseURL(t *testing.T) { - expectedBaseURL := "https://api.example.com/" - - got, err := NewClient("token", WithBaseURL(expectedBaseURL)) - if err != nil { - t.Fatal(err) - } - - if got.(*client).baseURL.String() != expectedBaseURL { - t.Fatalf("Expected the base URL to have been overriden") - } -} - -func TestWithBaseURLEmpty(t *testing.T) { - _, err := NewClient("token", WithBaseURL("")) - if err == nil { - t.Fatalf("Expected an error with an empty base URL provided") - } -} - -func TestWithBaseURLInvalid(t *testing.T) { - invalidBaseURL := "https:// api.example.com" - - _, err := NewClient("token", WithBaseURL(invalidBaseURL)) - if err == nil { - t.Fatalf("Expected an error with an invalid base URL provided") - } -} diff --git a/clerk/clerk_test.go b/clerk/clerk_test.go deleted file mode 100644 index 729f16c8..00000000 --- a/clerk/clerk_test.go +++ /dev/null @@ -1,276 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "reflect" - "testing" -) - -func TestNewClient_baseUrl(t *testing.T) { - c, err := NewClient("token") - if err != nil { - t.Errorf("NewClient failed") - } - - if got, want := c.(*client).baseURL.String(), ProdUrl; got != want { - t.Errorf("NewClient BaseURL is %v, want %v", got, want) - } -} - -func TestNewClient_baseUrlWithoutSlash(t *testing.T) { - input, want := "http://host/v1", "http://host/v1/" - c, _ := NewClient("token", WithBaseURL(input)) - - if got := c.(*client).baseURL.String(); got != want { - t.Errorf("NewClient BaseURL is %v, want %v", got, want) - } -} - -func TestNewClient_createsDifferentClients(t *testing.T) { - httpClient1, httpClient2 := &http.Client{}, &http.Client{} - - token := "token" - c1, _ := NewClient(token, WithHTTPClient(httpClient1)) - c2, _ := NewClient(token, WithHTTPClient(httpClient2)) - - if c1.(*client).client == c2.(*client).client { - t.Error("NewClient returned same http.Clients, but they should differ") - } -} - -func TestNewRequest(t *testing.T) { - client, _ := NewClient("token") - - inputUrl, outputUrl := "test", ProdUrl+"test" - method := "GET" - req, err := client.NewRequest(method, inputUrl) - if err != nil { - t.Errorf("NewRequest(%q, %s) method is generated error %v", inputUrl, method, err) - } - - if got, want := req.Method, method; got != want { - t.Errorf("NewRequest(%q, %s) method is %v, want %v", inputUrl, method, got, want) - } - - if got, want := req.URL.String(), outputUrl; got != want { - t.Errorf("NewRequest(%q, %s) URL is %v, want %v", inputUrl, method, got, want) - } -} - -func TestNewRequest_invalidUrl(t *testing.T) { - client, _ := NewClient("token") - _, err := client.NewRequest("GET", ":") - if err == nil { - t.Errorf("Expected error to be returned") - } - if err, ok := err.(*url.Error); !ok || err.Op != "parse" { - t.Errorf("Expected URL parse error, got %+v", err) - } -} - -func TestNewRequest_invalidMethod(t *testing.T) { - client, _ := NewClient("token") - invalidMethod := "ΠΟΣΤ" - _, err := client.NewRequest(invalidMethod, "/test") - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestNewRequest_noBody(t *testing.T) { - client, _ := NewClient("token") - req, _ := client.NewRequest("GET", ".") - if req.Body != nil { - t.Fatalf("Expected nil Body but request contains a non-nil Body") - } -} - -func TestNewRequest_nilBody(t *testing.T) { - client, _ := NewClient("token") - req, _ := client.NewRequest("GET", ".", nil) - if req.Body != nil { - t.Fatalf("Expected nil Body but request contains a non-nil Body") - } -} - -func TestNewRequest_withBody(t *testing.T) { - client, _ := NewClient("token") - - type Foo struct { - Key string `json:"key"` - } - - inBody, outBody := Foo{Key: "value"}, `{"key":"value"}`+"\n" - req, _ := client.NewRequest("GET", ".", inBody) - - body, _ := ioutil.ReadAll(req.Body) - if got, want := string(body), outBody; got != want { - t.Errorf("NewRequest(%q) Body is %v, want %v", inBody, got, want) - } -} - -func TestNewRequest_invalidBody(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.NewRequest("GET", ".", make(chan int)) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestDo_happyPath(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - type foo struct { - A string - } - - mux.HandleFunc("/test", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - fmt.Fprint(w, `{"A":"a"}`) - }) - - req, _ := client.NewRequest("GET", "test") - body := new(foo) - client.Do(req, body) - - want := &foo{"a"} - if !reflect.DeepEqual(body, want) { - t.Errorf("Response body = %v, want %v", body, want) - } -} - -func TestDo_sendsTokenInRequest(t *testing.T) { - token := "token" - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/test", func(w http.ResponseWriter, req *http.Request) { - testHeader(t, req, "Authorization", "Bearer "+token) - w.WriteHeader(http.StatusNoContent) - }) - - req, _ := client.NewRequest("GET", "test") - _, err := client.Do(req, nil) - if err != nil { - t.Errorf("Was not expecting any errors") - } -} - -func TestDo_invalidServer(t *testing.T) { - client, _ := NewClient("token", WithBaseURL("http://dummy_url:1337")) - - req, _ := client.NewRequest("GET", "test") - - // No server setup, should result in an error - _, err := client.Do(req, nil) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestDo_handlesClerkErrors(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expected := &ErrorResponse{ - Errors: []Error{{ - Message: "Error message", - LongMessage: "Error long message", - Code: "error_message", - }}, - } - - mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusBadRequest) - data, _ := json.Marshal(expected) - w.Write(data) - }) - - req, _ := client.NewRequest("GET", "test") - resp, err := client.Do(req, nil) - - if err == nil { - t.Fatal("Expected HTTP 400 error, got no error.") - } - if resp.StatusCode != 400 { - t.Fatalf("Expected HTTP 400 error, got %d status code.", resp.StatusCode) - } - - errorResponse, isClerkErr := err.(*ErrorResponse) - if !isClerkErr { - t.Fatal("Expected Clerk error response.") - } - if errorResponse.Response != nil { - t.Fatal("Expected error response to contain the HTTP response") - } - if !reflect.DeepEqual(errorResponse.Errors, expected.Errors) { - t.Fatalf("Actual = %v, want %v", errorResponse.Errors, expected.Errors) - } -} - -func TestDo_unexpectedHttpError(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(500) - }) - - req, _ := client.NewRequest("GET", "test") - resp, err := client.Do(req, nil) - - if err == nil { - t.Fatal("Expected HTTP 500 error, got no error.") - } - if resp.StatusCode != 500 { - t.Errorf("Expected HTTP 500 error, got %d status code.", resp.StatusCode) - } -} - -func TestDo_failToReadBody(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - type foo struct { - A string - } - - mux.HandleFunc("/test", func(w http.ResponseWriter, req *http.Request) { - // Lying about the body, telling client the length is 1 but not sending anything back - w.Header().Set("Content-Length", "1") - }) - - req, _ := client.NewRequest("GET", "test") - body := new(foo) - _, err := client.Do(req, body) - if err == nil { - t.Fatal("Expected EOF error, got no error.") - } -} - -func TestDo_failToUnmarshalBody(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - type foo struct { - A string - } - - mux.HandleFunc("/test", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - fmt.Fprint(w, `{invalid}`) - }) - - req, _ := client.NewRequest("GET", "test") - body := new(foo) - _, err := client.Do(req, body) - if err == nil { - t.Fatal("Expected JSON encoding error, got no error.") - } -} diff --git a/clerk/clients.go b/clerk/clients.go deleted file mode 100644 index 6f6d8979..00000000 --- a/clerk/clients.go +++ /dev/null @@ -1,48 +0,0 @@ -package clerk - -import "fmt" - -type ClientsService service - -type ClientResponse struct { - Object string `json:"object"` - ID string `json:"id"` - LastActiveSessionID *string `json:"last_active_session_id"` - SessionIDs []string `json:"session_ids"` - Sessions []*Session `json:"sessions"` - Ended bool `json:"ended"` -} - -func (s *ClientsService) ListAll() ([]ClientResponse, error) { - clientsUrl := "clients" - req, _ := s.client.NewRequest("GET", clientsUrl) - - var clients []ClientResponse - _, err := s.client.Do(req, &clients) - if err != nil { - return nil, err - } - return clients, nil -} - -func (s *ClientsService) Read(clientId string) (*ClientResponse, error) { - clientUrl := fmt.Sprintf("%s/%s", ClientsUrl, clientId) - req, _ := s.client.NewRequest("GET", clientUrl) - - var clientResponse ClientResponse - _, err := s.client.Do(req, &clientResponse) - if err != nil { - return nil, err - } - return &clientResponse, nil -} - -func (s *ClientsService) Verify(token string) (*ClientResponse, error) { - var clientResponse ClientResponse - - err := doVerify(s.client, ClientsVerifyUrl, token, &clientResponse) - if err != nil { - return nil, err - } - return &clientResponse, nil -} diff --git a/clerk/clients_test.go b/clerk/clients_test.go deleted file mode 100644 index ef4da2d5..00000000 --- a/clerk/clients_test.go +++ /dev/null @@ -1,134 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "reflect" - "testing" -) - -func TestClientsService_ListAll_happyPath(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := "[" + dummyClientResponseJson + "]" - - mux.HandleFunc("/clients", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - fmt.Fprint(w, expectedResponse) - }) - - var want []ClientResponse - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Clients().ListAll() - if len(got) != len(want) { - t.Errorf("Was expecting %d clients to be returned, instead got %d", len(want), len(got)) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestClientsService_ListAll_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - response, err := client.Clients().ListAll() - if err == nil { - t.Errorf("Expected error to be returned") - } - if response != nil { - t.Errorf("Was not expecting any clients to be returned, instead got %v", response) - } -} - -func TestClientsService_Read_happyPath(t *testing.T) { - token := "token" - clientId := "someClientId" - expectedResponse := dummyClientResponseJson - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/clients/"+clientId, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want ClientResponse - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Clients().Read(clientId) - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -func TestClientsService_Read_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - response, err := client.Clients().Read("someClientId") - if err == nil { - t.Errorf("Expected error to be returned") - } - if response != nil { - t.Errorf("Was not expecting any client to be returned, instead got %v", response) - } -} - -func TestClientsService_Verify_happyPath(t *testing.T) { - token := "token" - sessionToken := "sessionToken" - expectedResponse := dummyClientResponseJson - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/clients/verify", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want ClientResponse - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Clients().Verify(sessionToken) - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -func TestClientsService_Verify_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - response, err := client.Clients().Verify("someSessionToken") - if err == nil { - t.Errorf("Expected error to be returned") - } - if response != nil { - t.Errorf("Was not expecting any client to be returned, instead got %v", response) - } -} - -const dummyClientResponseJson = `{ - "ended": false, - "id": "client_1mvnkzXhKhn9pDjp1f4x1id6pQZ", - "last_active_session_id": "sess_1mebQdHlQI14cjxln4e2eXNzwzi", - "session_ids": ["sess_1mebQdHlQI14cjxln4e2eXNzwzi"], - "sessions": [{ - "id": "sess_1mebQdHlQI14cjxln4e2eXNzwzi", - "abandon_at": 1612448988, - "client_id": "client_1mebPYz8NFNA17fi7NemNXIwp1p", - "expire_at": 1610461788, - "last_active_at": 1609857251, - "object": "session", - "status": "ended", - "user_id": "user_1mebQggrD3xO5JfuHk7clQ94ysA" - }], - "object": "client" - }` diff --git a/clerk/delete_response.go b/clerk/delete_response.go deleted file mode 100644 index 3bb080e8..00000000 --- a/clerk/delete_response.go +++ /dev/null @@ -1,8 +0,0 @@ -package clerk - -type DeleteResponse struct { - ID string `json:"id,omitempty"` - Slug string `json:"slug,omitempty"` - Object string `json:"object"` - Deleted bool `json:"deleted"` -} diff --git a/clerk/domains.go b/clerk/domains.go deleted file mode 100644 index ed0890a1..00000000 --- a/clerk/domains.go +++ /dev/null @@ -1,91 +0,0 @@ -package clerk - -import ( - "fmt" - "net/http" -) - -type DomainsService service - -type DomainListResponse struct { - Data []Domain `json:"data"` - TotalCount int `json:"total_count"` -} - -type DomainCNameTarget struct { - Host string `json:"host"` - Value string `json:"value"` -} - -type Domain struct { - Object string `json:"object"` - ID string `json:"id"` - Name string `json:"name"` - IsSatellite bool `json:"is_satellite"` - FapiURL string `json:"frontend_api_url"` - AccountsPortalURL *string `json:"accounts_portal_url,omitempty"` - ProxyURL *string `json:"proxy_url,omitempty"` - CNameTargets []DomainCNameTarget `json:"cname_targets,omitempty"` - DevelopmentOrigin string `json:"development_origin"` -} - -type CreateDomainParams struct { - Name string `json:"name"` - IsSatellite bool `json:"is_satellite"` - ProxyURL *string `json:"proxy_url,omitempty"` -} - -type UpdateDomainParams struct { - Name *string `json:"name,omitempty"` - ProxyURL *string `json:"proxy_url,omitempty"` -} - -func (s *DomainsService) ListAll() (*DomainListResponse, error) { - req, _ := s.client.NewRequest(http.MethodGet, DomainsURL) - - var response *DomainListResponse - _, err := s.client.Do(req, &response) - if err != nil { - return nil, err - } - return response, nil -} - -func (s *DomainsService) Create( - params CreateDomainParams, -) (*Domain, error) { - req, _ := s.client.NewRequest(http.MethodPost, DomainsURL, ¶ms) - - var domain Domain - _, err := s.client.Do(req, &domain) - if err != nil { - return nil, err - } - return &domain, nil -} - -func (s *DomainsService) Update( - domainID string, - params UpdateDomainParams, -) (*Domain, error) { - url := fmt.Sprintf("%s/%s", DomainsURL, domainID) - req, _ := s.client.NewRequest(http.MethodPatch, url, ¶ms) - - var domain Domain - _, err := s.client.Do(req, &domain) - if err != nil { - return nil, err - } - return &domain, nil -} - -func (s *DomainsService) Delete(domainID string) (*DeleteResponse, error) { - url := fmt.Sprintf("%s/%s", DomainsURL, domainID) - req, _ := s.client.NewRequest(http.MethodDelete, url) - - var delResponse DeleteResponse - if _, err := s.client.Do(req, &delResponse); err != nil { - return nil, err - } - return &delResponse, nil -} diff --git a/clerk/domains_test.go b/clerk/domains_test.go deleted file mode 100644 index c6375adc..00000000 --- a/clerk/domains_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "reflect" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestDomainsService_ListAll_HappyPath(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := fmt.Sprintf(`{ - "total_count": 1, - "data": [%s] - }`, domainJSON) - - mux.HandleFunc("/domains", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - fmt.Fprint(w, expectedResponse) - }) - - var want *DomainListResponse - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Domains().ListAll() - if got.TotalCount != want.TotalCount { - t.Errorf("Was expecting %d domains to be returned, instead got %d", want.TotalCount, got.TotalCount) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestDomainsService_Create_HappyPath(t *testing.T) { - token := "token" - expectedResponse := domainJSON - - name := "foobar.com" - - payload := CreateDomainParams{ - Name: name, - IsSatellite: true, - } - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/domains", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want Domain - err := json.Unmarshal([]byte(expectedResponse), &want) - assert.Nil(t, err) - - got, err := client.Domains().Create(payload) - assert.Nil(t, err) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestDomainsService_Update_HappyPath(t *testing.T) { - token := "token" - domainID := "dmn_banana" - expectedResponse := domainJSON - - name := "foobar.com" - - payload := UpdateDomainParams{ - Name: &name, - } - - client, mux, _, teardown := setup(token) - defer teardown() - - url := fmt.Sprintf("/domains/%s", domainID) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "PATCH") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want Domain - err := json.Unmarshal([]byte(expectedResponse), &want) - assert.Nil(t, err) - - got, err := client.Domains().Update(domainID, payload) - assert.Nil(t, err) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestDomainsService_Delete_HappyPath(t *testing.T) { - token := "token" - domainID := "dmn_banana" - expectedResponse := `{ - "object": "domain", - "id": "dmn_banana", - "delete": "true" - }` - - client, mux, _, teardown := setup(token) - defer teardown() - - url := fmt.Sprintf("/domains/%s", domainID) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "DELETE") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want DeleteResponse - err := json.Unmarshal([]byte(expectedResponse), &want) - assert.Nil(t, err) - - got, err := client.Domains().Delete(domainID) - assert.Nil(t, err) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %#v, want %#v", got, want) - } -} - -const domainJSON = `{ - "id": "dmn_banana", - "name": "foobar.com" -}` diff --git a/clerk/email_addresses.go b/clerk/email_addresses.go deleted file mode 100644 index def3b81f..00000000 --- a/clerk/email_addresses.go +++ /dev/null @@ -1,72 +0,0 @@ -package clerk - -import "fmt" - -type EmailAddressesService service - -type EmailAddress struct { - ID string `json:"id"` - Object string `json:"object"` - EmailAddress string `json:"email_address"` - Reserved bool `json:"reserved"` - Verification *Verification `json:"verification"` - LinkedTo []IdentificationLink `json:"linked_to"` -} - -type CreateEmailAddressParams struct { - UserID string `json:"user_id"` - EmailAddress string `json:"email_address"` - Verified *bool `json:"verified"` - Primary *bool `json:"primary"` -} - -type UpdateEmailAddressParams struct { - Verified *bool `json:"verified"` - Primary *bool `json:"primary"` -} - -func (s *EmailAddressesService) Create(params CreateEmailAddressParams) (*EmailAddress, error) { - req, _ := s.client.NewRequest("POST", EmailAddressesURL, ¶ms) - - var emailAddress EmailAddress - _, err := s.client.Do(req, &emailAddress) - if err != nil { - return nil, err - } - return &emailAddress, nil -} - -func (s *EmailAddressesService) Read(emailAddressID string) (*EmailAddress, error) { - emailAddressURL := fmt.Sprintf("%s/%s", EmailAddressesURL, emailAddressID) - req, _ := s.client.NewRequest("GET", emailAddressURL) - - var emailAddress EmailAddress - _, err := s.client.Do(req, &emailAddress) - if err != nil { - return nil, err - } - return &emailAddress, nil -} - -func (s *EmailAddressesService) Update(emailAddressID string, params UpdateEmailAddressParams) (*EmailAddress, error) { - emailAddressURL := fmt.Sprintf("%s/%s", EmailAddressesURL, emailAddressID) - req, _ := s.client.NewRequest("PATCH", emailAddressURL, ¶ms) - - var emailAddress EmailAddress - _, err := s.client.Do(req, &emailAddress) - if err != nil { - return nil, err - } - return &emailAddress, nil -} - -func (s *EmailAddressesService) Delete(emailAddressID string) (*DeleteResponse, error) { - emailAddressURL := fmt.Sprintf("%s/%s", EmailAddressesURL, emailAddressID) - req, _ := s.client.NewRequest("DELETE", emailAddressURL) - - var delResponse DeleteResponse - if _, err := s.client.Do(req, &delResponse); err != nil { - return nil, err - } - return &delResponse, nil -} diff --git a/clerk/email_addresses_test.go b/clerk/email_addresses_test.go deleted file mode 100644 index 6e1e2f8f..00000000 --- a/clerk/email_addresses_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "github.com/stretchr/testify/assert" - "net/http" - "reflect" - "testing" -) - -func TestEmailAddressesService_Create_HappyPath(t *testing.T) { - token := "token" - expectedResponse := unverifiedEmailAddressJSON - - verified := false - primary := false - - payload := CreateEmailAddressParams{ - UserID: "user_abcdefg", - EmailAddress: "banana@cherry.com", - Verified: &verified, - Primary: &primary, - } - - client, mux, _, teardown := setup(token) - defer teardown() - - url := "/email_addresses" - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want EmailAddress - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, err := client.EmailAddresses().Create(payload) - assert.Nil(t, err) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestEmailAddressesService_Read_HappyPath(t *testing.T) { - token := "token" - emailAddressID := "idn_banana" - expectedResponse := unverifiedEmailAddressJSON - - client, mux, _, teardown := setup(token) - defer teardown() - - url := fmt.Sprintf("/email_addresses/%s", emailAddressID) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want EmailAddress - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, err := client.EmailAddresses().Read(emailAddressID) - assert.Nil(t, err) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestEmailAddressesService_Update_HappyPath(t *testing.T) { - token := "token" - emailAddressID := "idn_banana" - expectedResponse := verifiedEmailAddressJSON - - verified := true - primary := true - - payload := UpdateEmailAddressParams{ - Verified: &verified, - Primary: &primary, - } - - client, mux, _, teardown := setup(token) - defer teardown() - - url := fmt.Sprintf("/email_addresses/%s", emailAddressID) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "PATCH") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want EmailAddress - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, err := client.EmailAddresses().Update(emailAddressID, payload) - assert.Nil(t, err) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestEmailAddressesService_Delete_HappyPath(t *testing.T) { - token := "token" - emailAddressID := "idn_banana" - - client, mux, _, teardown := setup(token) - defer teardown() - - url := fmt.Sprintf("/email_addresses/%s", emailAddressID) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "DELETE") - testHeader(t, req, "Authorization", "Bearer "+token) - response := fmt.Sprintf(`{ "deleted": true, "id": "%v", "object": "email_address" }`, emailAddressID) - fmt.Fprint(w, response) - }) - - want := DeleteResponse{ID: emailAddressID, Object: "email_address", Deleted: true} - - got, _ := client.EmailAddresses().Delete(emailAddressID) - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -const unverifiedEmailAddressJSON = `{ - "id": "idn_banana", - "object": "email_address", - "email_address": "banana@cherry.com", - "reserved": true, - "linked_to": [] -}` - -const verifiedEmailAddressJSON = `{ - "id": "idn_banana", - "object": "email_address", - "email_address": "banana@cherry.com", - "reserved": true, - "verification": { - "status": "verified", - "strategy": "admin", - "attempts": null, - "expire_at": null - }, - "linked_to": [] -}` diff --git a/clerk/emails.go b/clerk/emails.go deleted file mode 100644 index 8b0b3aa3..00000000 --- a/clerk/emails.go +++ /dev/null @@ -1,35 +0,0 @@ -package clerk - -import ( - "encoding/json" -) - -type EmailService service - -type Email struct { - FromEmailName string `json:"from_email_name"` - Subject string `json:"subject"` - Body string `json:"body"` - EmailAddressID string `json:"email_address_id"` -} - -type EmailResponse struct { - ID string `json:"id"` - Object string `json:"object"` - Status string `json:"status,omitempty"` - ToEmailAddress *string `json:"to_email_address,omitempty"` - DeliveredByClerk bool `json:"delivered_by_clerk"` - Data json.RawMessage `json:"data"` - Email -} - -func (s *EmailService) Create(email Email) (*EmailResponse, error) { - req, _ := s.client.NewRequest("POST", EmailsUrl, &email) - - var emailResponse EmailResponse - _, err := s.client.Do(req, &emailResponse) - if err != nil { - return nil, err - } - return &emailResponse, nil -} diff --git a/clerk/emails_test.go b/clerk/emails_test.go deleted file mode 100644 index 3f85e2f6..00000000 --- a/clerk/emails_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "reflect" - "testing" -) - -func TestEmailService_Create_happyPath(t *testing.T) { - token := "token" - var email Email - _ = json.Unmarshal([]byte(dummyEmailJson), &email) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/emails", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyEmailJson) - }) - - got, _ := client.Emails().Create(email) - - var want EmailResponse - _ = json.Unmarshal([]byte(dummyEmailJson), &want) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, email) - } -} - -func TestEmailService_Create_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - var email Email - _ = json.Unmarshal([]byte(dummyEmailJson), &email) - - _, err := client.Emails().Create(email) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -const dummyEmailJson = `{ - "body": "This is the body of a test email", - "email_address_id": "idn_1mebQ9KkZWrhb9rL6iEiXQGF8Yj", - "from_email_name": "info", - "id": "ema_1mvFol71HiKCcypBd6xxg0IpMBN", - "object": "email", - "status": "queued", - "subject": "This is a test email", - "data": { "foo": "bar" }, - "delivered_by_clerk": false -}` diff --git a/clerk/errors.go b/clerk/errors.go deleted file mode 100644 index bbf8324c..00000000 --- a/clerk/errors.go +++ /dev/null @@ -1,24 +0,0 @@ -package clerk - -import ( - "fmt" - "net/http" -) - -type ErrorResponse struct { - Response *http.Response - Errors []Error `json:"errors"` -} - -type Error struct { - Message string `json:"message"` - LongMessage string `json:"long_message"` - Code string `json:"code"` - Meta interface{} `json:"meta,omitempty"` -} - -func (e *ErrorResponse) Error() string { - return fmt.Sprintf("%v %v: %d %+v", - e.Response.Request.Method, e.Response.Request.URL, - e.Response.StatusCode, e.Errors) -} diff --git a/clerk/http_utils_test.go b/clerk/http_utils_test.go deleted file mode 100644 index c6955411..00000000 --- a/clerk/http_utils_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package clerk - -import ( - "net/http" - "net/http/httptest" - "net/url" - "testing" -) - -// setup sets up a test HTTP server along with a `clerk.Client` that is configured to talk to that test server. -// Tests should register handlers on mux which provide mock responses for the API method being tested. -func setup(token string) (client Client, mux *http.ServeMux, serverURL *url.URL, teardown func()) { - versionPath := "/v1" - - mux = http.NewServeMux() - apiHandler := http.NewServeMux() - apiHandler.Handle(versionPath+"/", http.StripPrefix(versionPath, mux)) - - // server is a test HTTP server used to provide mock API responses. - server := httptest.NewServer(apiHandler) - - baseURL, _ := url.Parse(server.URL + versionPath + "/") - client, _ = NewClient(token, WithBaseURL(baseURL.String())) - - return client, mux, baseURL, server.Close -} - -func testHttpMethod(t *testing.T, r *http.Request, want string) { - t.Helper() - if got := r.Method; got != want { - t.Errorf("Request method: %v, want %v", got, want) - } -} - -func testHeader(t *testing.T, r *http.Request, header, want string) { - t.Helper() - if got := r.Header.Get(header); got != want { - t.Errorf("Header.Get(%q) returned %q, want %q", header, got, want) - } -} - -func testQuery(t *testing.T, r *http.Request, want url.Values) { - t.Helper() - - query := r.URL.Query() - - for k := range want { - if query.Get(k) == "" { - t.Errorf("Request query doesn't match: have %v, want %v", query, want) - } - } -} - -func stringToPtr(input string) *string { - return &input -} - -func intToPtr(input int) *int { - return &input -} - -func int64ToPtr(input int64) *int64 { - return &input -} diff --git a/clerk/instances.go b/clerk/instances.go deleted file mode 100644 index 037e925a..00000000 --- a/clerk/instances.go +++ /dev/null @@ -1,133 +0,0 @@ -package clerk - -import ( - "net/http" -) - -type InstanceService service - -type UpdateInstanceParams struct { - // TestMode can be used to toggle test mode for this instance. - // Defaults to true for development instances. - TestMode *bool `json:"test_mode,omitempty"` - - // HIBP is used to configure whether Clerk should use the - // "Have I Been Pawned" service to check passwords against - // known security breaches. - // By default, this is enabled in all instances. - HIBP *bool `json:"hibp,omitempty"` - - // EnhancedEmailDeliverability controls how Clerk delivers emails. - // Specifically, when set to true, if the instance is a production - // instance, OTP verification emails are sent by the Clerk's shared - // domain via Postmark. - EnhancedEmailDeliverability *bool `json:"enhanced_email_deliverability,omitempty"` - - // SupportEmail is the contact email address that will be displayed - // on the frontend, in case your instance users need support. - // If the empty string is provided, the support email that is currently - // configured in the instance will be removed. - SupportEmail *string `json:"support_email,omitempty"` - - // ClerkJSVersion allows you to request a specific Clerk JS version on the Clerk Hosted Account pages. - // If an empty string is provided, the stored version will be removed. - // If an explicit version is not set, the Clerk JS version will be automatically be resolved. - ClerkJSVersion *string `json:"clerk_js_version,omitempty"` - - // CookielessDev can be used to enable the new mode in which no third-party - // cookies are used in development instances. Make sure to also enable the - // setting in Clerk.js - // - // Deprecated: Use URLBasedSessionSyncing instead - CookielessDev *bool `json:"cookieless_dev,omitempty"` - - // URLBasedSessionSyncing can be used to enable the new mode in which no third-party - // cookies are used in development instances. Make sure to also enable the - // setting in Clerk.js - URLBasedSessionSyncing *bool `json:"url_based_session_syncing,omitempty"` - - // URL that is going to be used in development instances in order to create custom redirects - // and fix the third-party cookies issues. - DevelopmentOrigin *string `json:"development_origin,omitempty"` -} - -func (s *InstanceService) Update(params UpdateInstanceParams) error { - req, _ := s.client.NewRequest(http.MethodPatch, "instance", ¶ms) - - _, err := s.client.Do(req, nil) - return err -} - -type InstanceRestrictionsResponse struct { - Object string `json:"object"` - Allowlist bool `json:"allowlist"` - Blocklist bool `json:"blocklist"` - BlockEmailSubaddresses bool `json:"block_email_subaddresses"` - BlockDisposableEmailDomains bool `json:"block_disposable_email_domains"` -} - -type UpdateRestrictionsParams struct { - Allowlist *bool `json:"allowlist,omitempty"` - Blocklist *bool `json:"blocklist,omitempty"` - BlockEmailSubaddresses *bool `json:"block_email_subaddresses,omitempty"` - BlockDisposableEmailDomains *bool `json:"block_disposable_email_domains,omitempty"` -} - -func (s *InstanceService) UpdateRestrictions(params UpdateRestrictionsParams) (*InstanceRestrictionsResponse, error) { - req, _ := s.client.NewRequest(http.MethodPatch, "instance/restrictions", ¶ms) - - var instanceRestrictionsResponse InstanceRestrictionsResponse - _, err := s.client.Do(req, &instanceRestrictionsResponse) - if err != nil { - return nil, err - } - return &instanceRestrictionsResponse, nil -} - -type OrganizationSettingsResponse struct { - Object string `json:"object"` - Enabled bool `json:"enabled"` - MaxAllowedMemberships int `json:"max_allowed_memberships"` - MaxAllowedRoles int `json:"max_allowed_roles"` - MaxAllowedPermissions int `json:"max_allowed_permissions"` - CreatorRole string `json:"creator_role"` - AdminDeleteEnabled bool `json:"admin_delete_enabled"` - DomainsEnabled bool `json:"domains_enabled"` - DomainsEnrollmentModes []string `json:"domains_enrollment_modes"` - DomainsDefaultRole string `json:"domains_default_role"` -} - -type UpdateOrganizationSettingsParams struct { - Enabled *bool `json:"enabled,omitempty"` - MaxAllowedMemberships *int `json:"max_allowed_memberships,omitempty"` - CreatorRoleID *string `json:"creator_role_id,omitempty"` - AdminDeleteEnabled *bool `json:"admin_delete_enabled,omitempty"` - DomainsEnabled *bool `json:"domains_enabled,omitempty"` - DomainsEnrollmentModes []string `json:"domains_enrollment_modes,omitempty"` - DomainsDefaultRoleID *string `json:"domains_default_role_id,omitempty"` -} - -func (s *InstanceService) UpdateOrganizationSettings(params UpdateOrganizationSettingsParams) (*OrganizationSettingsResponse, error) { - req, _ := s.client.NewRequest(http.MethodPatch, "instance/organization_settings", ¶ms) - - var organizationSettingsResponse OrganizationSettingsResponse - _, err := s.client.Do(req, &organizationSettingsResponse) - if err != nil { - return nil, err - } - return &organizationSettingsResponse, nil -} - -type UpdateHomeURLParams struct { - HomeURL string `json:"home_url"` -} - -func (s *InstanceService) UpdateHomeURL(params UpdateHomeURLParams) error { - req, _ := s.client.NewRequest(http.MethodPost, "instance/change_domain", ¶ms) - - _, err := s.client.Do(req, nil) - if err != nil { - return err - } - return nil -} diff --git a/clerk/instances_test.go b/clerk/instances_test.go deleted file mode 100644 index 99a80af7..00000000 --- a/clerk/instances_test.go +++ /dev/null @@ -1,151 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestInstanceService_Update_happyPath(t *testing.T) { - token := "token" - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/instance", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPatch) - testHeader(t, req, "Authorization", "Bearer "+token) - w.WriteHeader(http.StatusNoContent) - }) - - enabled := true - supportEmail := "support@clerk.dev" - clerkJSVersion := "42" - err := client.Instances().Update(UpdateInstanceParams{ - TestMode: &enabled, - HIBP: &enabled, - EnhancedEmailDeliverability: &enabled, - SupportEmail: &supportEmail, - ClerkJSVersion: &clerkJSVersion, - }) - - if err != nil { - t.Errorf("expected no error to be returned, found %v instead", err) - } -} - -func TestInstanceService_Update_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - enabled := true - supportEmail := "support@clerk.dev" - clerkJSVersion := "999" - err := client.Instances().Update(UpdateInstanceParams{ - TestMode: &enabled, - HIBP: &enabled, - EnhancedEmailDeliverability: &enabled, - SupportEmail: &supportEmail, - ClerkJSVersion: &clerkJSVersion, - }) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestInstanceService_UpdateRestrictions_happyPath(t *testing.T) { - token := "token" - dummyRestrictionsResponseJSON := `{ - "allowlist": true, - "blocklist": true, - "block_email_subaddresses": true, - "block_disposable_email_domains": true - }` - var restrictionsResponse InstanceRestrictionsResponse - err := json.Unmarshal([]byte(dummyRestrictionsResponseJSON), &restrictionsResponse) - assert.NoError(t, err) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/instance/restrictions", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPatch) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyRestrictionsResponseJSON) - }) - - enabled := true - got, _ := client.Instances().UpdateRestrictions(UpdateRestrictionsParams{ - Allowlist: &enabled, - Blocklist: &enabled, - BlockEmailSubaddresses: &enabled, - BlockDisposableEmailDomains: &enabled, - }) - - assert.Equal(t, &restrictionsResponse, got) -} - -func TestInstanceService_UpdateRestrictions_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - enabled := true - _, err := client.Instances().UpdateRestrictions(UpdateRestrictionsParams{ - Allowlist: &enabled, - Blocklist: &enabled, - }) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestInstanceService_UpdateOrganizationSettings_happyPath(t *testing.T) { - token := "token" - dummyOrganizationSettingsResponseJSON := `{ - "enabled": true, - "max_allowed_memberships": 2, - "max_allowed_roles": 10, - "max_allowed_permissions": 50, - "creator_role": "org:custom_admin", - "admin_delete_enabled": true, - "domains_enabled": true, - "domains_enrollment_modes": [ - "manual_invitation", - "automatic_invitation", - "automatic_suggestion" - ], - "domains_default_role": "org:custom_domains" - }` - var organizationSettingsResponse OrganizationSettingsResponse - _ = json.Unmarshal([]byte(dummyOrganizationSettingsResponseJSON), &organizationSettingsResponse) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/instance/organization_settings", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPatch) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyOrganizationSettingsResponseJSON) - }) - - enabled := true - got, _ := client.Instances().UpdateOrganizationSettings(UpdateOrganizationSettingsParams{ - Enabled: &enabled, - CreatorRoleID: stringToPtr("role_2XcSZn6swGCjX59Nk0XbGer22jb"), - DomainsDefaultRoleID: stringToPtr("role_2XZCQwxfLbXOz2hoBXKFVRjwmGc"), - }) - - assert.Equal(t, &organizationSettingsResponse, got) -} - -func TestInstanceService_UpdateOrganizationSettings_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - enabled := true - _, err := client.Instances().UpdateOrganizationSettings(UpdateOrganizationSettingsParams{ - Enabled: &enabled, - }) - if err == nil { - t.Errorf("Expected error to be returned") - } -} diff --git a/clerk/jwks.go b/clerk/jwks.go deleted file mode 100644 index f7dc1d40..00000000 --- a/clerk/jwks.go +++ /dev/null @@ -1,23 +0,0 @@ -package clerk - -import ( - "net/http" - - "github.com/go-jose/go-jose/v3" -) - -type JWKSService service - -type JWKS jose.JSONWebKeySet - -func (s *JWKSService) ListAll() (*JWKS, error) { - req, _ := s.client.NewRequest(http.MethodGet, "jwks", nil) - - jwks := JWKS{} - _, err := s.client.Do(req, &jwks) - if err != nil { - return nil, err - } - - return &jwks, nil -} diff --git a/clerk/jwks_cache.go b/clerk/jwks_cache.go deleted file mode 100644 index 04924b7f..00000000 --- a/clerk/jwks_cache.go +++ /dev/null @@ -1,43 +0,0 @@ -package clerk - -import ( - "fmt" - "sync" - "time" - - "github.com/go-jose/go-jose/v3" -) - -type jwksCache struct { - sync.RWMutex - jwks *JWKS - expiresAt time.Time -} - -func (c *jwksCache) isInvalid() bool { - c.RLock() - defer c.RUnlock() - - return c.jwks == nil || len(c.jwks.Keys) == 0 || time.Now().After(c.expiresAt) -} - -func (c *jwksCache) set(jwks *JWKS) { - c.Lock() - defer c.Unlock() - - c.jwks = jwks - c.expiresAt = time.Now().Add(time.Hour) -} - -func (c *jwksCache) get(kid string) (*jose.JSONWebKey, error) { - c.RLock() - defer c.RUnlock() - - for _, key := range c.jwks.Keys { - if key.KeyID == kid { - return &key, nil - } - } - - return nil, fmt.Errorf("no jwk key found for kid %s", kid) -} diff --git a/clerk/jwks_test.go b/clerk/jwks_test.go deleted file mode 100644 index d90f675a..00000000 --- a/clerk/jwks_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "reflect" - "testing" -) - -func TestJWKSService_ListAll_happyPath(t *testing.T) { - c, mux, _, teardown := setup("token") - defer teardown() - - mux.HandleFunc("/jwks", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - fmt.Fprint(w, dummyJWKSJson) - }) - - want := &JWKS{} - _ = json.Unmarshal([]byte(dummyJWKSJson), want) - - got, _ := c.JWKS().ListAll() - - if !reflect.DeepEqual(got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestJWKSService_ListAll_invalidServer(t *testing.T) { - c, _ := NewClient("token") - - jwks, err := c.JWKS().ListAll() - if err == nil { - t.Errorf("Expected error to be returned") - } - - if jwks != nil { - t.Errorf("Was not expecting any jwks to be returned, instead got %+v", jwks) - } -} - -const dummyJWKSJson = ` -{ - "keys": - [ - { - "use":"sig", - "kty":"RSA", - "kid":"kid", - "alg":"RS256", - "n":"8ffrRMLd1z50B1hJcEfoxPac2wm9U_SXCnoXxSg5frRyW1oI1t9e78y8sOOwUt-IU4FXNcNK93dsCDQMeDBc6EfLxPBHuCB4SbVvsbpdMH8XSy9qLH6AJmS1GqOldYG0VkP1YzSwGXTkflgcDLCtYOHxkjiK6m5TnhJ4tu77bkjPrINiWAo4jAYBCjk1gqiW3LZWZwzwvqF_7n8g50JbhoTiJi2z6rd0anSFgi1A9AbViKwlzdxkll1uW90W1kn_Zs6lC6Yz7-X9WmelhxxUoLVE49BcCQ82PtmlBvxDQk7rREPLRbvzJSI0RIw1HMChRkZC_KtsLNkgPKq5tY_YSw", - "e":"AQAB" - } - ] - } -` diff --git a/clerk/jwt_templates.go b/clerk/jwt_templates.go deleted file mode 100644 index c450326a..00000000 --- a/clerk/jwt_templates.go +++ /dev/null @@ -1,143 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" -) - -type JWTTemplatesService service - -type JWTTemplate struct { - ID string `json:"id"` - Object string `json:"object"` - Name string `json:"name"` - Claims json.RawMessage `json:"claims"` - Lifetime int `json:"lifetime"` - AllowedClockSkew int `json:"allowed_clock_skew"` - CustomSigningKey bool `json:"custom_signing_key"` - SigningAlgorithm string `json:"signing_algorithm"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -type CreateUpdateJWTTemplate struct { - Name string `json:"name"` - Claims map[string]interface{} `json:"claims"` - Lifetime *int `json:"lifetime"` - AllowedClockSkew *int `json:"allowed_clock_skew"` - - CustomSigningKey bool `json:"custom_signing_key"` - SigningAlgorithm *string `json:"signing_algorithm"` - SigningKey *string `json:"signing_key"` -} - -func (t CreateUpdateJWTTemplate) toRequest() (*createUpdateJWTTemplateRequest, error) { - claimsBytes, err := json.Marshal(t.Claims) - if err != nil { - return nil, err - } - - return &createUpdateJWTTemplateRequest{ - Claims: string(claimsBytes), - Name: t.Name, - Lifetime: t.Lifetime, - AllowedClockSkew: t.AllowedClockSkew, - CustomSigningKey: t.CustomSigningKey, - SigningAlgorithm: t.SigningAlgorithm, - SigningKey: t.SigningKey, - }, nil -} - -type createUpdateJWTTemplateRequest struct { - Claims string `json:"claims"` - Name string `json:"name"` - Lifetime *int `json:"lifetime,omitempty"` - AllowedClockSkew *int `json:"allowed_clock_skew,omitempty"` - CustomSigningKey bool `json:"custom_signing_key"` - SigningAlgorithm *string `json:"signing_algorithm,omitempty"` - SigningKey *string `json:"signing_key,omitempty"` -} - -func (s JWTTemplatesService) ListAll() ([]JWTTemplate, error) { - req, err := s.client.NewRequest(http.MethodGet, JWTTemplatesUrl) - if err != nil { - return nil, err - } - - jwtTemplates := make([]JWTTemplate, 0) - if _, err = s.client.Do(req, &jwtTemplates); err != nil { - return nil, err - } - - return jwtTemplates, nil -} - -func (s JWTTemplatesService) Read(id string) (*JWTTemplate, error) { - reqURL := fmt.Sprintf("%s/%s", JWTTemplatesUrl, id) - req, err := s.client.NewRequest(http.MethodGet, reqURL) - if err != nil { - return nil, err - } - - jwtTemplate := &JWTTemplate{} - if _, err = s.client.Do(req, jwtTemplate); err != nil { - return nil, err - } - - return jwtTemplate, nil -} - -func (s JWTTemplatesService) Create(params *CreateUpdateJWTTemplate) (*JWTTemplate, error) { - reqBody, err := params.toRequest() - if err != nil { - return nil, err - } - - req, err := s.client.NewRequest(http.MethodPost, JWTTemplatesUrl, reqBody) - if err != nil { - return nil, err - } - - resp := JWTTemplate{} - if _, err = s.client.Do(req, &resp); err != nil { - return nil, err - } - - return &resp, nil -} - -func (s JWTTemplatesService) Update(id string, params *CreateUpdateJWTTemplate) (*JWTTemplate, error) { - reqURL := fmt.Sprintf("%s/%s", JWTTemplatesUrl, id) - - reqBody, err := params.toRequest() - if err != nil { - return nil, err - } - - req, err := s.client.NewRequest(http.MethodPatch, reqURL, reqBody) - if err != nil { - return nil, err - } - - resp := JWTTemplate{} - if _, err = s.client.Do(req, &resp); err != nil { - return nil, err - } - - return &resp, nil -} - -func (s JWTTemplatesService) Delete(id string) (*DeleteResponse, error) { - reqURL := fmt.Sprintf("%s/%s", JWTTemplatesUrl, id) - req, err := s.client.NewRequest(http.MethodDelete, reqURL) - if err != nil { - return nil, err - } - - resp := &DeleteResponse{} - if _, err = s.client.Do(req, resp); err != nil { - return nil, err - } - return resp, nil -} diff --git a/clerk/jwt_templates_test.go b/clerk/jwt_templates_test.go deleted file mode 100644 index 87d968df..00000000 --- a/clerk/jwt_templates_test.go +++ /dev/null @@ -1,235 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "reflect" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestJWTTemplatesService_ListAll(t *testing.T) { - c, mux, _, teardown := setup("token") - defer teardown() - - dummyResponse := "[" + dummyJWTTemplateJSON + "]" - - mux.HandleFunc("/jwt_templates", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodGet) - testHeader(t, req, "Authorization", "Bearer token") - _, _ = fmt.Fprint(w, dummyResponse) - }) - - got, err := c.JWTTemplates().ListAll() - assert.Nil(t, err) - - expected := make([]JWTTemplate, 0) - _ = json.Unmarshal([]byte(dummyResponse), &expected) - - if !reflect.DeepEqual(got, expected) { - t.Errorf("Response = %v, want %v", got, expected) - } -} - -func TestJWTTemplatesService_Read(t *testing.T) { - dummyResponse := dummyTemplateJSON - - c, mux, _, teardown := setup("token") - defer teardown() - - url := fmt.Sprintf("/jwt_templates/%s", dummyJWTTemplateID) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodGet) - testHeader(t, req, "Authorization", "Bearer token") - _, _ = fmt.Fprint(w, dummyResponse) - }) - - got, err := c.JWTTemplates().Read(dummyJWTTemplateID) - assert.Nil(t, err) - - expected := JWTTemplate{} - _ = json.Unmarshal([]byte(dummyResponse), &expected) - - if !reflect.DeepEqual(*got, expected) { - t.Errorf("Response = %v, want %v", got, expected) - } -} - -func TestJWTTemplatesService_Create(t *testing.T) { - dummyResponse := dummyJWTTemplateJSON - - c, mux, _, teardown := setup("token") - defer teardown() - - mux.HandleFunc("/jwt_templates", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPost) - testHeader(t, req, "Authorization", "Bearer token") - _, _ = fmt.Fprint(w, dummyResponse) - }) - - newJWTTmpl := &CreateUpdateJWTTemplate{ - Name: "Testing", - Claims: map[string]interface{}{ - "name": "{{user.first_name}}", - "role": "tester", - }, - } - - got, err := c.JWTTemplates().Create(newJWTTmpl) - assert.Nil(t, err) - - expected := JWTTemplate{} - _ = json.Unmarshal([]byte(dummyResponse), &expected) - - if !reflect.DeepEqual(*got, expected) { - t.Errorf("Response = %v, want %v", got, expected) - } -} - -func TestJWTTemplatesService_CreateWithCustomSigningKey(t *testing.T) { - c, mux, _, teardown := setup("token") - defer teardown() - - customSigningAlgorithm := "HS256" - customSigningKey := "random-secret" - - mux.HandleFunc("/jwt_templates", func(w http.ResponseWriter, r *http.Request) { - testHttpMethod(t, r, http.MethodPost) - testHeader(t, r, "Authorization", "Bearer token") - - var req createUpdateJWTTemplateRequest - _ = json.NewDecoder(r.Body).Decode(&req) - - assert.Equal(t, true, req.CustomSigningKey) - assert.Equal(t, customSigningAlgorithm, *req.SigningAlgorithm) - assert.Equal(t, customSigningKey, *req.SigningKey) - - _, _ = fmt.Fprint(w, dummyJWTTemplateCustomSigningKeyJSON) - }) - - newJWTTmpl := &CreateUpdateJWTTemplate{ - Name: "Testing-Custom-Signing-Key", - Claims: map[string]interface{}{ - "name": "{{user.first_name}}", - "role": "tester", - }, - CustomSigningKey: true, - SigningAlgorithm: &customSigningAlgorithm, - SigningKey: &customSigningKey, - } - - _, err := c.JWTTemplates().Create(newJWTTmpl) - assert.Nil(t, err) -} - -func TestJWTTemplatesService_Update(t *testing.T) { - dummyResponse := dummyJWTTemplateCustomLifetimeAndClockSkewJSON - - c, mux, _, teardown := setup("token") - defer teardown() - - url := fmt.Sprintf("/jwt_templates/%s", dummyJWTTemplateID) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPatch) - testHeader(t, req, "Authorization", "Bearer token") - _, _ = fmt.Fprint(w, dummyResponse) - }) - - updateJWTTmpl := &CreateUpdateJWTTemplate{ - Name: "New-Testing", - Claims: map[string]interface{}{ - "name": "{{user.first_name}}", - "age": 28, - }, - } - - got, err := c.JWTTemplates().Update(dummyJWTTemplateID, updateJWTTmpl) - assert.Nil(t, err) - - expected := JWTTemplate{} - _ = json.Unmarshal([]byte(dummyResponse), &expected) - - if !reflect.DeepEqual(*got, expected) { - t.Errorf("Response = %v, want %v", got, expected) - } -} - -func TestJWTTemplatesService_Delete(t *testing.T) { - c, mux, _, teardown := setup("token") - defer teardown() - - url := fmt.Sprintf("/jwt_templates/%s", dummyJWTTemplateID) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodDelete) - testHeader(t, req, "Authorization", "Bearer token") - response := fmt.Sprintf(`{ "deleted": true, "id": "%s", "object": "jwt_template" }`, dummyJWTTemplateID) - _, _ = fmt.Fprint(w, response) - }) - - expected := DeleteResponse{ - ID: dummyJWTTemplateID, - Object: "jwt_template", - Deleted: true, - } - - got, err := c.JWTTemplates().Delete(dummyJWTTemplateID) - assert.Nil(t, err) - - if !reflect.DeepEqual(*got, expected) { - t.Errorf("Response = %v, want %v", *got, expected) - } -} - -const ( - dummyJWTTemplateID = "jtmp_21xC2Ziqscwjq43MtC3CN6Pngbo" - - dummyJWTTemplateJSON = ` -{ - "object": "jwt_template", - "id": "` + dummyJWTTemplateID + `", - "name": "Testing", - "claims": { - "name": "{{user.first_name}}", - "role": "tester" - }, - "lifetime": 60, - "allowed_clock_skew": 5, - "custom_signing_key": false, - "signing_algorithm": "RS256" -}` - - dummyJWTTemplateCustomLifetimeAndClockSkewJSON = ` -{ - "object": "jwt_template", - "id": "` + dummyJWTTemplateID + `", - "name": "New-Testing", - "claims": { - "name": "{{user.first_name}}", - "age": 28 - }, - "lifetime": 60, - "allowed_clock_skew": 5, - "custom_signing_key": false, - "signing_algorithm": "RS256" -}` - - dummyJWTTemplateCustomSigningKeyJSON = ` -{ - "object": "jwt_template", - "id": "` + dummyJWTTemplateID + `", - "name": "Testing", - "claims": { - "name": "{{user.first_name}}", - "role": "tester" - }, - "lifetime": 60, - "allowed_clock_skew": 5, - "custom_signing_key": true, - "signing_algorithm": "HS256" -}` -) diff --git a/clerk/middleware.go b/clerk/middleware.go deleted file mode 100644 index 47ade1ae..00000000 --- a/clerk/middleware.go +++ /dev/null @@ -1,71 +0,0 @@ -package clerk - -import ( - "context" - "net/http" - "strings" -) - -const ( - ActiveSession = iota - ActiveSessionClaims - -// TODO: we should use a type alias instead of int, so as to avoid collisions -// with other packages -) - -// Deprecated: this middleware handles the old authentication scheme. Use -// WithSessionV2 instead. -func WithSession(client Client) func(handler http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if token, isAuthV2 := isAuthV2Request(r, client); isAuthV2 { - // Validate using session token - claims, err := client.VerifyToken(token) - if err != nil { - w.WriteHeader(http.StatusUnauthorized) - _, _ = w.Write([]byte(err.Error())) - return - } - - ctx := context.WithValue(r.Context(), ActiveSessionClaims, claims) - next.ServeHTTP(w, r.WithContext(ctx)) - } else { - // Validate using session verify request - session, err := client.Verification().Verify(r) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(err.Error())) - return - } - - ctx := context.WithValue(r.Context(), ActiveSession, session) - next.ServeHTTP(w, r.WithContext(ctx)) - } - }) - } -} - -func isAuthV2Request(r *http.Request, client Client) (string, bool) { - // Try with token from header - headerToken := strings.TrimSpace(r.Header.Get("Authorization")) - headerToken = strings.TrimPrefix(headerToken, "Bearer ") - - claims, err := client.DecodeToken(headerToken) - if err == nil { - return headerToken, newIssuer(claims.Issuer).IsValid() - } - - // Verification from header token failed, try with token from cookie - cookieSession, err := r.Cookie(CookieSession) - if err != nil { - return "", false - } - - claims, err = client.DecodeToken(cookieSession.Value) - if err != nil { - return "", false - } - - return cookieSession.Value, newIssuer(claims.Issuer).IsValid() -} diff --git a/clerk/middleware_test.go b/clerk/middleware_test.go deleted file mode 100644 index fb1cc927..00000000 --- a/clerk/middleware_test.go +++ /dev/null @@ -1,213 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "reflect" - "testing" - "time" - - "github.com/go-jose/go-jose/v3" - "github.com/go-jose/go-jose/v3/jwt" -) - -func TestWithSession_addSessionToContext(t *testing.T) { - apiToken := "apiToken" - sessionId := "someSessionId" - sessionToken := "someSessionToken" - - client, mux, serverUrl, teardown := setup(apiToken) - defer teardown() - - expectedResponse := dummySessionJson - - mux.HandleFunc("/sessions/"+sessionId+"/verify", func(w http.ResponseWriter, req *http.Request) { - fmt.Fprint(w, expectedResponse) - }) - - dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // this handler should be called after the middleware has added the `ActiveSession` - activeSession := r.Context().Value(ActiveSession) - resp, _ := json.Marshal(activeSession) - fmt.Fprint(w, string(resp)) - }) - - mux.Handle("/session", WithSession(client)(dummyHandler)) - - request := setupRequest(&sessionId, &sessionToken) - request.URL.Host = serverUrl.Host - request.URL.Path = "/v1/session" - - var got Session - _, _ = client.Do(request, &got) - - var want Session - _ = json.Unmarshal([]byte(expectedResponse), &want) - - if !reflect.DeepEqual(got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestWithSession_returnsErrorIfVerificationFails(t *testing.T) { - apiToken := "apiToken" - sessionId := "someSessionId" - sessionToken := "someSessionToken" - - client, mux, serverUrl, teardown := setup(apiToken) - defer teardown() - - mux.HandleFunc("/sessions/"+sessionId+"/verify", func(w http.ResponseWriter, req *http.Request) { - // return error - w.WriteHeader(400) - }) - - dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // this handler should be called after the middleware has added the `ActiveSession` - t.Errorf("This should never be called!") - }) - - mux.Handle("/session", WithSession(client)(dummyHandler)) - - request := setupRequest(&sessionId, &sessionToken) - request.URL.Host = serverUrl.Host - request.URL.Path = "/v1/session" - - resp, _ := client.Do(request, nil) - - if resp.StatusCode != http.StatusBadRequest { - t.Errorf("Was expecting 400 error code, found %v instead", resp.StatusCode) - } -} - -func TestWithSession_addSessionClaimsToContext_Header(t *testing.T) { - c, mux, serverUrl, teardown := setup("apiToken") - defer teardown() - - expectedClaims := dummySessionClaims - - token, pubKey := testGenerateTokenJWT(t, expectedClaims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - - dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // this handler should be called after the middleware has added the `ActiveClaims` - claims := r.Context().Value(ActiveSessionClaims) - _ = json.NewEncoder(w).Encode(claims) - }) - - mux.Handle("/claims", WithSession(c)(dummyHandler)) - - req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/claims", serverUrl), nil) - req.Header.Set("Authorization", token) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatal(err) - } - - defer resp.Body.Close() - - var got SessionClaims - _ = json.NewDecoder(resp.Body).Decode(&got) - - if !reflect.DeepEqual(got, expectedClaims) { - t.Errorf("Response = %v, want %v", got, expectedClaims) - } -} - -func TestWithSession_addSessionClaimsToContext_Cookie(t *testing.T) { - c, mux, serverUrl, teardown := setup("apiToken") - defer teardown() - - expectedClaims := dummySessionClaims - - token, pubKey := testGenerateTokenJWT(t, expectedClaims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - - dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // this handler should be called after the middleware has added the `ActiveClaims` - activeClaims := r.Context().Value(ActiveSessionClaims) - _ = json.NewEncoder(w).Encode(activeClaims) - }) - - mux.Handle("/claims", WithSession(c)(dummyHandler)) - - req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/claims", serverUrl), nil) - req.AddCookie(&http.Cookie{ - Name: "__session", - Value: token, - Secure: true, - HttpOnly: true, - }) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatal(err) - } - - defer resp.Body.Close() - - var got SessionClaims - _ = json.NewDecoder(resp.Body).Decode(&got) - - if !reflect.DeepEqual(got, expectedClaims) { - t.Errorf("Response = %v, want %v", got, expectedClaims) - } -} - -func TestWithSession_returnsErrorIfTokenVerificationFails(t *testing.T) { - c, mux, serverUrl, teardown := setup("apiToken") - defer teardown() - - expectedClaims := dummySessionClaims - expectedClaims.Expiry = jwt.NewNumericDate(time.Now().Add(time.Second * -1)) - - token, pubKey := testGenerateTokenJWT(t, expectedClaims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - - dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // this handler should be called after the middleware has added the `ActiveClaims` - activeClaims := r.Context().Value(ActiveSessionClaims) - _ = json.NewEncoder(w).Encode(activeClaims) - }) - - mux.Handle("/claims", WithSession(c)(dummyHandler)) - - req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/claims", serverUrl), nil) - req.Header.Set("Authorization", token) - - resp, _ := http.DefaultClient.Do(req) - - if resp.StatusCode != http.StatusUnauthorized { - t.Errorf("Was expecting 401 error code, found %v instead", resp.StatusCode) - } -} - -func TestWithSession_returnsErrorIfTokenMissing(t *testing.T) { - apiToken := "apiToken" - - c, mux, serverUrl, teardown := setup(apiToken) - defer teardown() - - dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // this handler should be called after the middleware has added the `ActiveSession` - t.Errorf("This should never be called!") - }) - - mux.Handle("/claims", WithSession(c)(dummyHandler)) - - req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/claims", serverUrl), nil) - - resp, _ := http.DefaultClient.Do(req) - - if resp.StatusCode != http.StatusBadRequest { - t.Errorf("Was expecting 400 error code, found %v instead", resp.StatusCode) - } -} diff --git a/clerk/middleware_v2.go b/clerk/middleware_v2.go deleted file mode 100644 index 7569c2f2..00000000 --- a/clerk/middleware_v2.go +++ /dev/null @@ -1,200 +0,0 @@ -package clerk - -import ( - "context" - "errors" - "net" - "net/http" - "net/url" - "regexp" - "strconv" - "strings" - - "github.com/go-jose/go-jose/v3/jwt" -) - -var urlSchemeRe = regexp.MustCompile(`(^\w+:|^)\/\/`) - -// RequireSessionV2 will hijack the request and return an HTTP status 403 -// if the session is not authenticated. -func RequireSessionV2(client Client, verifyTokenOptions ...VerifyTokenOption) func(handler http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - f := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - claims, ok := r.Context().Value(ActiveSessionClaims).(*SessionClaims) - if !ok || claims == nil { - w.WriteHeader(http.StatusForbidden) - return - } - - next.ServeHTTP(w, r) - }) - - return WithSessionV2(client, verifyTokenOptions...)(f) - } -} - -// SessionFromContext returns the session's (if any) claims, as parsed from the -// token. -func SessionFromContext(ctx context.Context) (*SessionClaims, bool) { - c, ok := ctx.Value(ActiveSessionClaims).(*SessionClaims) - return c, ok -} - -// WithSessionV2 is the de-facto authentication middleware and should be -// preferred to WithSession. If the session is authenticated, it adds the corresponding -// session claims found in the JWT to request's context. -func WithSessionV2(client Client, verifyTokenOptions ...VerifyTokenOption) func(handler http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // **************************************************** - // * - // HEADER AUTHENTICATION * - // * - // **************************************************** - _, authorizationHeaderExists := r.Header["Authorization"] - - if authorizationHeaderExists { - headerToken := strings.TrimSpace(r.Header.Get("Authorization")) - headerToken = strings.TrimPrefix(headerToken, "Bearer ") - - _, err := client.DecodeToken(headerToken) - if err != nil { - // signed out - next.ServeHTTP(w, r) - return - } - - claims, err := client.VerifyToken(headerToken, verifyTokenOptions...) - if err == nil { // signed in - ctx := context.WithValue(r.Context(), ActiveSessionClaims, claims) - next.ServeHTTP(w, r.WithContext(ctx)) - return - } - - // Clerk.js should refresh the token and retry - w.WriteHeader(http.StatusUnauthorized) - return - } - - // In development or staging environments only, based on the request User Agent, detect non-browser - // requests (e.g. scripts). If there is no Authorization header, consider the user as signed out - // and prevent interstitial rendering - if isDevelopmentOrStaging(client) && !strings.HasPrefix(r.UserAgent(), "Mozilla/") { - // signed out - next.ServeHTTP(w, r) - return - } - - // in cross-origin requests the use of Authorization - // header is mandatory - if isCrossOrigin(r) { - // signed out - next.ServeHTTP(w, r) - return - } - - // **************************************************** - // * - // COOKIE AUTHENTICATION * - // * - // **************************************************** - cookieToken, _ := r.Cookie("__session") - clientUat, _ := r.Cookie("__client_uat") - - if isDevelopmentOrStaging(client) && (r.Referer() == "" || isCrossOrigin(r)) { - renderInterstitial(client, w) - return - } - - if isProduction(client) && clientUat == nil { - next.ServeHTTP(w, r) - return - } - - if clientUat != nil && clientUat.Value == "0" { - next.ServeHTTP(w, r) - return - } - - if clientUat == nil { - renderInterstitial(client, w) - return - } - - var clientUatTs int64 - ts, err := strconv.ParseInt(clientUat.Value, 10, 64) - if err == nil { - clientUatTs = ts - } - - if cookieToken == nil { - renderInterstitial(client, w) - return - } - - claims, err := client.VerifyToken(cookieToken.Value, verifyTokenOptions...) - - if err == nil { - if claims.IssuedAt != nil && clientUatTs <= int64(*claims.IssuedAt) { - ctx := context.WithValue(r.Context(), ActiveSessionClaims, claims) - next.ServeHTTP(w, r.WithContext(ctx)) - return - } - - renderInterstitial(client, w) - return - } - - if errors.Is(err, jwt.ErrExpired) || errors.Is(err, jwt.ErrIssuedInTheFuture) { - renderInterstitial(client, w) - return - } - - // signed out - next.ServeHTTP(w, r) - return - }) - } -} - -func isCrossOrigin(r *http.Request) bool { - // origin contains scheme+host and optionally port (ommitted if 80 or 443) - // ref. https://www.rfc-editor.org/rfc/rfc6454#section-6.1 - origin := strings.TrimSpace(r.Header.Get("Origin")) - origin = urlSchemeRe.ReplaceAllString(origin, "") // strip scheme - if origin == "" { - return false - } - - // parse request's host and port, taking into account reverse proxies - u := &url.URL{Host: r.Host} - host := strings.TrimSpace(r.Header.Get("X-Forwarded-Host")) - if host == "" { - host = u.Hostname() - } - port := strings.TrimSpace(r.Header.Get("X-Forwarded-Port")) - if port == "" { - port = u.Port() - } - - if port != "" && port != "80" && port != "443" { - host = net.JoinHostPort(host, port) - } - - return origin != host -} - -func isDevelopmentOrStaging(c Client) bool { - return strings.HasPrefix(c.APIKey(), "test_") || strings.HasPrefix(c.APIKey(), "sk_test_") -} - -func isProduction(c Client) bool { - return !isDevelopmentOrStaging(c) -} - -func renderInterstitial(c Client, w http.ResponseWriter) { - w.Header().Set("content-type", "text/html") - w.WriteHeader(401) - resp, _ := c.Interstitial() - w.Write(resp) -} diff --git a/clerk/middleware_v2_test.go b/clerk/middleware_v2_test.go deleted file mode 100644 index dc19db25..00000000 --- a/clerk/middleware_v2_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package clerk - -import ( - "fmt" - "net/http" - "testing" -) - -func TestWithSessionV2_nonBrowserRequest(t *testing.T) { - c, mux, serverUrl, teardown := setup("test_dummy") - defer teardown() - - dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Should be signed out - _, ok := r.Context().Value(ActiveSessionClaims).(*SessionClaims) - if ok { - t.Error("Expected session claims not to be present in request context") - } - }) - - mux.Handle("/dummy", WithSessionV2(c)(dummyHandler)) - - req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/dummy", serverUrl), nil) - req.Header.Set("User-Agent", "curl/7.64.1") - - _, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatal(err) - } -} - -func TestWithSessionV2_emptyAuthorizationHeader(t *testing.T) { - c, mux, serverUrl, teardown := setup("test_dummy") - defer teardown() - - dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Should be signed out - _, ok := r.Context().Value(ActiveSessionClaims).(*SessionClaims) - if ok { - t.Error("Expected session claims not to be present in request context") - } - }) - - mux.Handle("/dummy", WithSessionV2(c)(dummyHandler)) - - req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/dummy", serverUrl), nil) - req.Header.Set("Authorization", "") - - _, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatal(err) - } -} diff --git a/clerk/organizations.go b/clerk/organizations.go deleted file mode 100644 index db7bf2e4..00000000 --- a/clerk/organizations.go +++ /dev/null @@ -1,387 +0,0 @@ -package clerk - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "mime/multipart" - "net/http" - "strconv" -) - -type OrganizationsService service - -type Organization struct { - Object string `json:"object"` - ID string `json:"id"` - Name string `json:"name"` - Slug *string `json:"slug"` - LogoURL *string `json:"logo_url"` - ImageURL *string `json:"image_url,omitempty"` - HasImage bool `json:"has_image"` - MembersCount *int `json:"members_count,omitempty"` - MaxAllowedMemberships int `json:"max_allowed_memberships"` - AdminDeleteEnabled bool `json:"admin_delete_enabled"` - PublicMetadata json.RawMessage `json:"public_metadata"` - PrivateMetadata json.RawMessage `json:"private_metadata,omitempty"` - CreatedBy string `json:"created_by"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -type CreateOrganizationParams struct { - Name string `json:"name"` - Slug *string `json:"slug,omitempty"` - CreatedBy string `json:"created_by"` - MaxAllowedMemberships *int `json:"max_allowed_memberships,omitempty"` - PublicMetadata json.RawMessage `json:"public_metadata,omitempty"` - PrivateMetadata json.RawMessage `json:"private_metadata,omitempty"` -} - -func (s *OrganizationsService) Create(params CreateOrganizationParams) (*Organization, error) { - req, _ := s.client.NewRequest(http.MethodPost, OrganizationsUrl, ¶ms) - - var organization Organization - _, err := s.client.Do(req, &organization) - if err != nil { - return nil, err - } - return &organization, nil -} - -type UpdateOrganizationParams struct { - Name *string `json:"name,omitempty"` - Slug *string `json:"slug,omitempty"` - MaxAllowedMemberships *int `json:"max_allowed_memberships,omitempty"` - AdminDeleteEnabled *bool `json:"admin_delete_enabled,omitempty"` - PublicMetadata json.RawMessage `json:"public_metadata,omitempty"` - PrivateMetadata json.RawMessage `json:"private_metadata,omitempty"` -} - -func (s *OrganizationsService) Update(organizationID string, params UpdateOrganizationParams) (*Organization, error) { - req, _ := s.client.NewRequest(http.MethodPatch, fmt.Sprintf("%s/%s", OrganizationsUrl, organizationID), ¶ms) - - var organization Organization - _, err := s.client.Do(req, &organization) - if err != nil { - return nil, err - } - return &organization, nil -} - -type UpdateOrganizationLogoParams struct { - File multipart.File - UploaderUserID string - Filename *string -} - -func (s *OrganizationsService) UpdateLogo(organizationID string, params UpdateOrganizationLogoParams) (*Organization, error) { - var buf bytes.Buffer - w := multipart.NewWriter(&buf) - uploaderUserID, err := w.CreateFormField("uploader_user_id") - if err != nil { - return nil, err - } - uploaderUserID.Write([]byte(params.UploaderUserID)) - - filename := "file" - if params.Filename != nil { - filename = *params.Filename - } - file, err := w.CreateFormFile("file", filename) - if err != nil { - return nil, err - } - defer params.File.Close() - _, err = io.Copy(file, params.File) - if err != nil { - return nil, err - } - w.Close() - - req, err := s.client.NewRequest(http.MethodPut, fmt.Sprintf("%s/%s/logo", OrganizationsUrl, organizationID)) - if err != nil { - return nil, err - } - req.Body = io.NopCloser(&buf) - req.Header.Set("content-type", w.FormDataContentType()) - - var organization Organization - _, err = s.client.Do(req, &organization) - return &organization, err -} - -func (s *OrganizationsService) DeleteLogo(organizationID string) (*Organization, error) { - req, _ := s.client.NewRequest(http.MethodDelete, fmt.Sprintf("%s/%s/logo", OrganizationsUrl, organizationID)) - var organization Organization - _, err := s.client.Do(req, &organization) - return &organization, err -} - -type UpdateOrganizationMetadataParams struct { - PublicMetadata json.RawMessage `json:"public_metadata,omitempty"` - PrivateMetadata json.RawMessage `json:"private_metadata,omitempty"` -} - -func (s *OrganizationsService) UpdateMetadata(organizationID string, params UpdateOrganizationMetadataParams) (*Organization, error) { - req, _ := s.client.NewRequest(http.MethodPatch, fmt.Sprintf("%s/%s/metadata", OrganizationsUrl, organizationID), ¶ms) - - var organization Organization - _, err := s.client.Do(req, &organization) - return &organization, err -} - -func (s *OrganizationsService) Delete(organizationID string) (*DeleteResponse, error) { - req, _ := s.client.NewRequest(http.MethodDelete, fmt.Sprintf("%s/%s", OrganizationsUrl, organizationID)) - - var deleteResponse DeleteResponse - _, err := s.client.Do(req, &deleteResponse) - if err != nil { - return nil, err - } - return &deleteResponse, nil -} - -func (s *OrganizationsService) Read(organizationIDOrSlug string) (*Organization, error) { - req, err := s.client.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s", OrganizationsUrl, organizationIDOrSlug)) - if err != nil { - return nil, err - } - - var organization Organization - _, err = s.client.Do(req, &organization) - if err != nil { - return nil, err - } - return &organization, nil -} - -type OrganizationsResponse struct { - Data []Organization `json:"data"` - TotalCount int64 `json:"total_count"` -} - -type ListAllOrganizationsParams struct { - Limit *int - Offset *int - IncludeMembersCount bool - Query string - UserIDs []string - OrderBy *string -} - -func (s *OrganizationsService) ListAll(params ListAllOrganizationsParams) (*OrganizationsResponse, error) { - req, _ := s.client.NewRequest(http.MethodGet, OrganizationsUrl) - - query := req.URL.Query() - if params.Limit != nil { - query.Set("limit", strconv.Itoa(*params.Limit)) - } - if params.Offset != nil { - query.Set("offset", strconv.Itoa(*params.Offset)) - } - if params.IncludeMembersCount { - query.Set("include_members_count", strconv.FormatBool(params.IncludeMembersCount)) - } - if params.Query != "" { - query.Add("query", params.Query) - } - if params.OrderBy != nil { - query.Add("order_by", *params.OrderBy) - } - for _, userID := range params.UserIDs { - query.Add("user_id", userID) - } - req.URL.RawQuery = query.Encode() - - var organizationsResponse *OrganizationsResponse - _, err := s.client.Do(req, &organizationsResponse) - if err != nil { - return nil, err - } - return organizationsResponse, nil -} - -type OrganizationInvitation struct { - Object string `json:"object"` - ID string `json:"id"` - EmailAddress string `json:"email_address"` - OrganizationID string `json:"organization_id"` - PublicMetadata json.RawMessage `json:"public_metadata"` - Role string `json:"role"` - Status string `json:"status"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -type CreateOrganizationInvitationParams struct { - EmailAddress string `json:"email_address"` - InviterUserID string `json:"inviter_user_id"` - OrganizationID string `json:"organization_id"` - PublicMetadata json.RawMessage `json:"public_metadata,omitempty"` - RedirectURL string `json:"redirect_url,omitempty"` - Role string `json:"role"` -} - -func (s *OrganizationsService) CreateInvitation(params CreateOrganizationInvitationParams) (*OrganizationInvitation, error) { - endpoint := fmt.Sprintf("%s/%s/%s", OrganizationsUrl, params.OrganizationID, InvitationsURL) - req, _ := s.client.NewRequest(http.MethodPost, endpoint, ¶ms) - var organizationInvitation OrganizationInvitation - _, err := s.client.Do(req, &organizationInvitation) - return &organizationInvitation, err -} - -type ListOrganizationMembershipsParams struct { - OrganizationID string - Limit *int - Offset *int - Roles []string `json:"role"` - UserIDs []string `json:"user_id"` - EmailAddresses []string `json:"email_address"` - PhoneNumbers []string `json:"phone_number"` - Usernames []string `json:"username"` - Web3Wallets []string `json:"web3_wallet"` - OrderBy *string `json:"order_by"` - Query *string `json:"query"` -} - -type ΟrganizationMembershipPublicUserData struct { - FirstName *string `json:"first_name"` - LastName *string `json:"last_name"` - ProfileImageURL string `json:"profile_image_url"` - ImageURL *string `json:"image_url,omitempty"` - HasImage bool `json:"has_image"` - Identifier string `json:"identifier"` - UserID string `json:"user_id"` -} - -type OrganizationMembership struct { - Object string `json:"object"` - ID string `json:"id"` - PublicMetadata json.RawMessage `json:"public_metadata"` - PrivateMetadata json.RawMessage `json:"private_metadata"` - Role string `json:"role"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` - - Organization *Organization `json:"organization"` - PublicUserData *ΟrganizationMembershipPublicUserData `json:"public_user_data"` -} - -type ListOrganizationMembershipsResponse struct { - Data []OrganizationMembership `json:"data"` - TotalCount int64 `json:"total_count"` -} - -func (s *OrganizationsService) addMembersSearchParamsToRequest(r *http.Request, params ListOrganizationMembershipsParams) { - query := r.URL.Query() - for _, email := range params.EmailAddresses { - query.Add("email_address", email) - } - - for _, phone := range params.PhoneNumbers { - query.Add("phone_number", phone) - } - - for _, web3Wallet := range params.Web3Wallets { - query.Add("web3_wallet", web3Wallet) - } - - for _, username := range params.Usernames { - query.Add("username", username) - } - - for _, userID := range params.UserIDs { - query.Add("user_id", userID) - } - - for _, role := range params.Roles { - query.Add("role", role) - } - - if params.Query != nil { - query.Add("query", *params.Query) - } - - if params.OrderBy != nil { - query.Add("order_by", *params.OrderBy) - } - - r.URL.RawQuery = query.Encode() -} - -func (s *OrganizationsService) ListMemberships(params ListOrganizationMembershipsParams) (*ListOrganizationMembershipsResponse, error) { - endpoint := fmt.Sprintf("%s/%s/memberships", OrganizationsUrl, params.OrganizationID) - req, _ := s.client.NewRequest(http.MethodGet, endpoint) - - s.addMembersSearchParamsToRequest(req, ListOrganizationMembershipsParams{ - EmailAddresses: params.EmailAddresses, - PhoneNumbers: params.PhoneNumbers, - Web3Wallets: params.Web3Wallets, - Usernames: params.Usernames, - UserIDs: params.UserIDs, - Roles: params.Roles, - Query: params.Query, - OrderBy: params.OrderBy, - }) - - query := req.URL.Query() - if params.Limit != nil { - query.Set("limit", strconv.Itoa(*params.Limit)) - } - if params.Offset != nil { - query.Set("offset", strconv.Itoa(*params.Offset)) - } - req.URL.RawQuery = query.Encode() - - var membershipsResponse *ListOrganizationMembershipsResponse - _, err := s.client.Do(req, &membershipsResponse) - if err != nil { - return nil, err - } - return membershipsResponse, nil -} - -type CreateOrganizationMembershipParams struct { - UserID string `json:"user_id"` - Role string `json:"role"` -} - -func (s *OrganizationsService) CreateMembership(organizationID string, params CreateOrganizationMembershipParams) (*OrganizationMembership, error) { - req, _ := s.client.NewRequest(http.MethodPost, fmt.Sprintf("%s/%s/memberships", OrganizationsUrl, organizationID), ¶ms) - - var organizationMembership OrganizationMembership - _, err := s.client.Do(req, &organizationMembership) - if err != nil { - return nil, err - } - return &organizationMembership, nil -} - -type UpdateOrganizationMembershipParams struct { - UserID string `json:"user_id"` - Role string `json:"role"` -} - -func (s *OrganizationsService) UpdateMembership(organizationID string, params UpdateOrganizationMembershipParams) (*OrganizationMembership, error) { - req, _ := s.client.NewRequest(http.MethodPatch, fmt.Sprintf("%s/%s/memberships/%s", OrganizationsUrl, organizationID, params.UserID), ¶ms) - - var organizationMembership OrganizationMembership - _, err := s.client.Do(req, &organizationMembership) - if err != nil { - return nil, err - } - return &organizationMembership, nil -} - -func (s *OrganizationsService) DeleteMembership(organizationID, userID string) (*OrganizationMembership, error) { - req, _ := s.client.NewRequest(http.MethodDelete, fmt.Sprintf("%s/%s/memberships/%s", OrganizationsUrl, organizationID, userID)) - - var organizationMembership OrganizationMembership - _, err := s.client.Do(req, &organizationMembership) - if err != nil { - return nil, err - } - return &organizationMembership, nil -} diff --git a/clerk/organizations_test.go b/clerk/organizations_test.go deleted file mode 100644 index 29ef6a53..00000000 --- a/clerk/organizations_test.go +++ /dev/null @@ -1,343 +0,0 @@ -package clerk - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "net/url" - "os" - "path" - "reflect" - "strings" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestOrganizationsService_Read(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := dummyOrganizationJson - orgID := "randomIDorSlug" - - mux.HandleFunc(fmt.Sprintf("/organizations/%s", orgID), func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - fmt.Fprint(w, expectedResponse) - }) - - got, err := client.Organizations().Read(orgID) - if err != nil { - t.Fatal(err) - } - - var want Organization - err = json.Unmarshal([]byte(expectedResponse), &want) - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(got, &want) { - t.Errorf("Response = %v, want %v", got, &want) - } -} - -func TestOrganizationsService_Update(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - var payload UpdateOrganizationParams - _ = json.Unmarshal([]byte(dummyUpdateOrganizationJson), &payload) - - expectedResponse := dummyOrganizationJson - orgID := "randomIDorSlug" - - mux.HandleFunc(fmt.Sprintf("/organizations/%s", orgID), func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "PATCH") - testHeader(t, req, "Authorization", "Bearer token") - fmt.Fprint(w, expectedResponse) - }) - - got, err := client.Organizations().Update(orgID, payload) - if err != nil { - t.Fatal(err) - } - - var want Organization - err = json.Unmarshal([]byte(expectedResponse), &want) - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(got, &want) { - t.Errorf("Response = %v, want %v", got, &want) - } -} - -func TestOrganizationsService_invalidServer(t *testing.T) { - client, _ := NewClient("token") - var payload UpdateOrganizationParams - _ = json.Unmarshal([]byte(dummyUpdateOrganizationJson), &payload) - - _, err := client.Organizations().Update("someOrgId", payload) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestOrganizationsService_ListAll_happyPath(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := fmt.Sprintf(`{ - "data": [%s], - "total_count": 1 - }`, dummyOrganizationJson) - - mux.HandleFunc("/organizations", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - fmt.Fprint(w, expectedResponse) - }) - - var want *OrganizationsResponse - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Organizations().ListAll(ListAllOrganizationsParams{}) - if len(got.Data) != len(want.Data) { - t.Errorf("Was expecting %d organizations to be returned, instead got %d", len(want.Data), len(got.Data)) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestOrganizationsService_ListAll_happyPathWithParameters(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := fmt.Sprintf(`{ - "data": [%s], - "total_count": 1 - }`, dummyOrganizationJson) - - mux.HandleFunc("/organizations", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - - actualQuery := req.URL.Query() - expectedQuery := url.Values(map[string][]string{ - "limit": {"5"}, - "offset": {"6"}, - "include_members_count": {"true"}, - }) - assert.Equal(t, expectedQuery, actualQuery) - fmt.Fprint(w, expectedResponse) - }) - - var want *OrganizationsResponse - _ = json.Unmarshal([]byte(expectedResponse), &want) - - limit := 5 - offset := 6 - got, _ := client.Organizations().ListAll(ListAllOrganizationsParams{ - Limit: &limit, - Offset: &offset, - IncludeMembersCount: true, - }) - if len(got.Data) != len(want.Data) { - t.Errorf("Was expecting %d organizations to be returned, instead got %d", len(want.Data), len(got.Data)) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestOrganizationsService_ListAll_happyPathWithQuery(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := fmt.Sprintf(`{ - "data": [%s], - "total_count": 1 - }`, dummyOrganizationJson) - - mux.HandleFunc("/organizations", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - - actualQuery := req.URL.Query() - expectedQuery := url.Values(map[string][]string{ - "query": {"test"}, - }) - assert.Equal(t, expectedQuery, actualQuery) - fmt.Fprint(w, expectedResponse) - }) - - var want *OrganizationsResponse - _ = json.Unmarshal([]byte(expectedResponse), &want) - got, _ := client.Organizations().ListAll(ListAllOrganizationsParams{ - Query: "test", - }) - if len(got.Data) != len(want.Data) { - t.Errorf("Was expecting %d organizations to be returned, instead got %d", len(want.Data), len(got.Data)) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestOrganizationsService_ListAll_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - organizations, err := client.Organizations().ListAll(ListAllOrganizationsParams{}) - if err == nil { - t.Errorf("Expected error to be returned") - } - if organizations != nil { - t.Errorf("Was not expecting any organizations to be returned, instead got %v", organizations) - } -} - -func TestOrganizationsService_UpdateLogo(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - organizationID := "org_123" - uploaderUserID := "user_123" - expectedResponse := fmt.Sprintf(`{"id":"%s"}`, organizationID) - filename := "200x200-grayscale.jpg" - file, err := os.Open(path.Join("..", "testdata", filename)) - if err != nil { - t.Fatal(err) - } - defer file.Close() - - mux.HandleFunc( - fmt.Sprintf("/organizations/%s/logo", organizationID), - func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPut) - testHeader(t, req, "Authorization", "Bearer token") - // Assert that the request is sent as multipart/form-data - if !strings.Contains(req.Header["Content-Type"][0], "multipart/form-data") { - t.Errorf("expected content-type to be multipart/form-data, got %s", req.Header["Content-Type"]) - } - defer req.Body.Close() - - // Check that the file is sent correctly - fileParam, header, err := req.FormFile("file") - if err != nil { - t.Fatal(err) - } - if header.Filename != filename { - t.Errorf("expected %s, got %s", filename, header.Filename) - } - defer fileParam.Close() - - got := make([]byte, header.Size) - gotSize, err := fileParam.Read(got) - if err != nil { - t.Fatal(err) - } - fileInfo, err := file.Stat() - if err != nil { - t.Fatal(err) - } - want := make([]byte, fileInfo.Size()) - _, err = file.Seek(0, 0) - if err != nil { - t.Fatal(err) - } - wantSize, err := file.Read(want) - if err != nil { - t.Fatal(err) - } - if gotSize != wantSize { - t.Errorf("read different size of files") - } - if !bytes.Equal(got, want) { - t.Errorf("file was not sent correctly") - } - - // Check the uploader user ID - if got, ok := req.MultipartForm.Value["uploader_user_id"]; !ok || got[0] != uploaderUserID { - t.Errorf("expected %s, got %s", uploaderUserID, got) - } - - fmt.Fprint(w, expectedResponse) - }, - ) - - // Trigger a request to update the logo with the file - org, err := client.Organizations().UpdateLogo(organizationID, UpdateOrganizationLogoParams{ - File: file, - Filename: &filename, - UploaderUserID: "user_123", - }) - if err != nil { - t.Fatal(err) - } - if org.ID != organizationID { - t.Errorf("expected %s, got %s", organizationID, org.ID) - } -} - -func TestOrganizationsService_DeleteLogo(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - organizationID := "org_123" - mux.HandleFunc( - fmt.Sprintf("/organizations/%s/logo", organizationID), - func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodDelete) - testHeader(t, req, "Authorization", "Bearer token") - fmt.Fprint(w, fmt.Sprintf(`{"id":"%s"}`, organizationID)) - }, - ) - - // Trigger a request to delete the logo - _, err := client.Organizations().DeleteLogo(organizationID) - if err != nil { - t.Fatal(err) - } -} - -const dummyOrganizationJson = `{ - "object": "organization", - "id": "org_1mebQggrD3xO5JfuHk7clQ94ysA", - "name": "test-org", - "slug": "org_slug", - "members_count": 42, - "created_by": "user_1mebQggrD3xO5JfuHk7clQ94ysA", - "created_at": 1610783813, - "updated_at": 1610783813, - "public_metadata": { - "address": { - "street": "Pennsylvania Avenue", - "number": "1600" - } - }, - "private_metadata": { - "app_id": 5 - } -}` - -const dummyUpdateOrganizationJson = `{ - "object": "organization", - "id": "org_1mebQggrD3xO5JfuHk7clQ94ysA", - "name": "test-org", - "slug": "org_slug", - "members_count": 42, - "created_by": "user_1mebQggrD3xO5JfuHk7clQ94ysA", - "created_at": 1610783813, - "updated_at": 1610783813, - "public_metadata": {}, - "private_metadata": { - "app_id": 8, - } -}` diff --git a/clerk/phone_numbers.go b/clerk/phone_numbers.go deleted file mode 100644 index ee297e2e..00000000 --- a/clerk/phone_numbers.go +++ /dev/null @@ -1,75 +0,0 @@ -package clerk - -import "fmt" - -type PhoneNumbersService service - -type PhoneNumber struct { - ID string `json:"id"` - Object string `json:"object"` - PhoneNumber string `json:"phone_number"` - ReservedForSecondFactor bool `json:"reserved_for_second_factor"` - DefaultSecondFactor bool `json:"default_second_factor"` - Reserved bool `json:"reserved"` - Verification *Verification `json:"verification"` - LinkedTo []IdentificationLink `json:"linked_to"` - BackupCodes []string `json:"backup_codes"` -} - -type CreatePhoneNumberParams struct { - UserID string `json:"user_id"` - PhoneNumber string `json:"phone_number"` - Verified *bool `json:"verified"` - Primary *bool `json:"primary"` -} - -type UpdatePhoneNumberParams struct { - Verified *bool `json:"verified"` - Primary *bool `json:"primary"` -} - -func (s *PhoneNumbersService) Create(params CreatePhoneNumberParams) (*PhoneNumber, error) { - req, _ := s.client.NewRequest("POST", PhoneNumbersURL, ¶ms) - - var response PhoneNumber - _, err := s.client.Do(req, &response) - if err != nil { - return nil, err - } - return &response, nil -} - -func (s *PhoneNumbersService) Read(phoneNumberID string) (*PhoneNumber, error) { - phoneNumberURL := fmt.Sprintf("%s/%s", PhoneNumbersURL, phoneNumberID) - req, _ := s.client.NewRequest("GET", phoneNumberURL) - - var phoneNumber PhoneNumber - _, err := s.client.Do(req, &phoneNumber) - if err != nil { - return nil, err - } - return &phoneNumber, nil -} - -func (s *PhoneNumbersService) Update(phoneNumberID string, params UpdatePhoneNumberParams) (*PhoneNumber, error) { - phoneNumberURL := fmt.Sprintf("%s/%s", PhoneNumbersURL, phoneNumberID) - req, _ := s.client.NewRequest("PATCH", phoneNumberURL, ¶ms) - - var phoneNumber PhoneNumber - _, err := s.client.Do(req, &phoneNumber) - if err != nil { - return nil, err - } - return &phoneNumber, nil -} - -func (s *PhoneNumbersService) Delete(phoneNumberID string) (*DeleteResponse, error) { - phoneNumberURL := fmt.Sprintf("%s/%s", PhoneNumbersURL, phoneNumberID) - req, _ := s.client.NewRequest("DELETE", phoneNumberURL) - - var delResponse DeleteResponse - if _, err := s.client.Do(req, &delResponse); err != nil { - return nil, err - } - return &delResponse, nil -} diff --git a/clerk/phone_numbers_test.go b/clerk/phone_numbers_test.go deleted file mode 100644 index 56ba8fe9..00000000 --- a/clerk/phone_numbers_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "github.com/stretchr/testify/assert" - "net/http" - "reflect" - "testing" -) - -func TestPhoneNumbersService_Create_HappyPath(t *testing.T) { - token := "token" - expectedResponse := unverifiedPhoneNumberJSON - - verified := false - primary := false - - payload := CreatePhoneNumberParams{ - UserID: "user_abcdefg", - PhoneNumber: "+15555555555", - Verified: &verified, - Primary: &primary, - } - - client, mux, _, teardown := setup(token) - defer teardown() - - url := "/phone_numbers" - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want PhoneNumber - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, err := client.PhoneNumbers().Create(payload) - assert.Nil(t, err) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestPhoneNumbersService_Read_HappyPath(t *testing.T) { - token := "token" - phoneNumberID := "idn_banana" - expectedResponse := unverifiedPhoneNumberJSON - - client, mux, _, teardown := setup(token) - defer teardown() - - url := fmt.Sprintf("/phone_numbers/%s", phoneNumberID) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want PhoneNumber - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, err := client.PhoneNumbers().Read(phoneNumberID) - assert.Nil(t, err) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestPhoneNumbersService_Update_HappyPath(t *testing.T) { - token := "token" - phoneNumberID := "idn_banana" - expectedResponse := verifiedPhoneNumberJSON - - verified := true - primary := true - - payload := UpdatePhoneNumberParams{ - Verified: &verified, - Primary: &primary, - } - - client, mux, _, teardown := setup(token) - defer teardown() - - url := fmt.Sprintf("/phone_numbers/%s", phoneNumberID) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "PATCH") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want PhoneNumber - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, err := client.PhoneNumbers().Update(phoneNumberID, payload) - assert.Nil(t, err) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestPhoneNumbersService_Delete_HappyPath(t *testing.T) { - token := "token" - phoneNumberID := "idn_banana" - - client, mux, _, teardown := setup(token) - defer teardown() - - url := fmt.Sprintf("/phone_numbers/%s", phoneNumberID) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "DELETE") - testHeader(t, req, "Authorization", "Bearer "+token) - response := fmt.Sprintf(`{ "deleted": true, "id": "%v", "object": "phone_number" }`, phoneNumberID) - fmt.Fprint(w, response) - }) - - want := DeleteResponse{ID: phoneNumberID, Object: "phone_number", Deleted: true} - - got, _ := client.PhoneNumbers().Delete(phoneNumberID) - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -const unverifiedPhoneNumberJSON = `{ - "id": "idn_avocado", - "object": "phone_number", - "phone_number": "+15555555555", - "reserved_for_second_factor": false, - "default_second_factor": false, - "reserved": false, - "linked_to": [], - "backup_codes": null -}` - -const verifiedPhoneNumberJSON = `{ - "id": "idn_avocado", - "object": "phone_number", - "phone_number": "+15555555555", - "reserved_for_second_factor": false, - "default_second_factor": false, - "reserved": false, - "verification": { - "status": "verified", - "strategy": "admin", - "attempts": null, - "expire_at": null - }, - "linked_to": [], - "backup_codes": null -}` diff --git a/clerk/proxy_checks.go b/clerk/proxy_checks.go deleted file mode 100644 index 2202b615..00000000 --- a/clerk/proxy_checks.go +++ /dev/null @@ -1,31 +0,0 @@ -package clerk - -import "net/http" - -type ProxyChecksService service - -type ProxyCheck struct { - Object string `json:"object"` - ID string `json:"id"` - DomainID string `json:"domain_id"` - ProxyURL string `json:"proxy_url"` - Successful bool `json:"successful"` - LastRunAt *int64 `json:"last_run_at"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -type CreateProxyCheckParams struct { - DomainID string `json:"domain_id"` - ProxyURL string `json:"proxy_url"` -} - -func (s *ProxyChecksService) Create(params CreateProxyCheckParams) (*ProxyCheck, error) { - req, _ := s.client.NewRequest(http.MethodPost, ProxyChecksURL, ¶ms) - var proxyCheck ProxyCheck - _, err := s.client.Do(req, &proxyCheck) - if err != nil { - return nil, err - } - return &proxyCheck, nil -} diff --git a/clerk/proxy_checks_test.go b/clerk/proxy_checks_test.go deleted file mode 100644 index db8473c4..00000000 --- a/clerk/proxy_checks_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "reflect" - "testing" -) - -func TestProxyChecksService_Create(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - var params CreateProxyCheckParams - payload := `{ - "proxy_url":"https://example.com/__clerk", - "domain_id": "dmn_1mebQggrD3xO5JfuHk7clQ94ysA" -}` - _ = json.Unmarshal([]byte(payload), ¶ms) - - mux.HandleFunc("/proxy_checks", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - testHeader(t, req, "Authorization", "Bearer token") - fmt.Fprint(w, dummyProxyCheckJSON) - }) - - got, err := client.ProxyChecks().Create(params) - if err != nil { - t.Fatal(err) - } - - var want ProxyCheck - err = json.Unmarshal([]byte(dummyProxyCheckJSON), &want) - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(got, &want) { - t.Errorf("Response = %v, want %v", got, &want) - } -} - -const dummyProxyCheckJSON = `{ - "object": "proxy_check", - "id": "proxychk_1mebQggrD3xO5JfuHk7clQ94ysA", - "successful": true, - "domain_id": "dmn_1mebQggrD3xO5JfuHk7clQ94ysA", - "proxy_url": "https://example.com/__clerk", - "last_run_at": 1610783813, - "created_at": 1610783813, - "updated_at": 1610783813 -}` diff --git a/clerk/redirect_urls.go b/clerk/redirect_urls.go deleted file mode 100644 index 4c294b19..00000000 --- a/clerk/redirect_urls.go +++ /dev/null @@ -1,50 +0,0 @@ -package clerk - -import "net/http" - -type RedirectURLsService service - -type RedirectURLResponse struct { - Object string `json:"object"` - ID string `json:"id"` - URL string `json:"url"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -type CreateRedirectURLParams struct { - URL string `json:"url"` -} - -func (s *RedirectURLsService) Create(params CreateRedirectURLParams) (*RedirectURLResponse, error) { - req, _ := s.client.NewRequest(http.MethodPost, RedirectURLsUrl, ¶ms) - - var redirectURLResponse RedirectURLResponse - _, err := s.client.Do(req, &redirectURLResponse) - if err != nil { - return nil, err - } - return &redirectURLResponse, nil -} - -func (s *RedirectURLsService) ListAll() ([]*RedirectURLResponse, error) { - req, _ := s.client.NewRequest(http.MethodGet, RedirectURLsUrl, nil) - - var redirectURLResponses []*RedirectURLResponse - _, err := s.client.Do(req, &redirectURLResponses) - if err != nil { - return nil, err - } - return redirectURLResponses, nil -} - -func (s *RedirectURLsService) Delete(redirectURLID string) (*DeleteResponse, error) { - req, _ := s.client.NewRequest(http.MethodDelete, RedirectURLsUrl+"/"+redirectURLID, nil) - - var deleteResponse DeleteResponse - _, err := s.client.Do(req, &deleteResponse) - if err != nil { - return nil, err - } - return &deleteResponse, nil -} diff --git a/clerk/redirect_urls_test.go b/clerk/redirect_urls_test.go deleted file mode 100644 index efe8d9f2..00000000 --- a/clerk/redirect_urls_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestRedirectURLService_Create_happyPath(t *testing.T) { - token := "token" - var redirectURLResponse RedirectURLResponse - _ = json.Unmarshal([]byte(dummyRedirectURLJson), &redirectURLResponse) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/redirect_urls", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPost) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyRedirectURLJson) - }) - - got, err := client.RedirectURLs().Create(CreateRedirectURLParams{ - URL: redirectURLResponse.URL, - }) - - assert.Nil(t, err) - assert.Equal(t, *got, redirectURLResponse) -} - -func TestRedirectURLService_Create_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.RedirectURLs().Create(CreateRedirectURLParams{ - URL: "example.com", - }) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestRedirectURLService_ListAll_happyPath(t *testing.T) { - token := "token" - var redirectURLResponse RedirectURLResponse - _ = json.Unmarshal([]byte(dummyRedirectURLJson), &redirectURLResponse) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/redirect_urls", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodGet) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, "["+dummyRedirectURLJson+"]") - }) - - got, err := client.RedirectURLs().ListAll() - - assert.Nil(t, err) - assert.Equal(t, got, []*RedirectURLResponse{&redirectURLResponse}) -} - -func TestRedirectURLService_ListAll_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.RedirectURLs().ListAll() - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestRedirectURLService_Delete_happyPath(t *testing.T) { - token := "token" - client, mux, _, teardown := setup(token) - defer teardown() - - id := "some_id" - mux.HandleFunc("/redirect_urls/"+id, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodDelete) - testHeader(t, req, "Authorization", "Bearer "+token) - - response := fmt.Sprintf(`{ "deleted": true, "id": "%v", "object": "user" }`, id) - fmt.Fprint(w, response) - }) - - got, err := client.RedirectURLs().Delete(id) - assert.Nil(t, err) - assert.NotNil(t, got) -} - -func TestRedirectURLService_Delete_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.RedirectURLs().Delete("random_id") - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -const dummyRedirectURLJson = `{ - "object": "redirect_url", - "id": "ru_1mvFol71HiKCcypBd6xxg0IpMBN", - "url": "example.com" -}` diff --git a/clerk/saml_connections.go b/clerk/saml_connections.go deleted file mode 100644 index 9efe85bb..00000000 --- a/clerk/saml_connections.go +++ /dev/null @@ -1,140 +0,0 @@ -package clerk - -import ( - "fmt" - "net/http" -) - -type SAMLConnectionsService service - -type SAMLConnection struct { - ID string `json:"id"` - Object string `json:"object"` - Name string `json:"name"` - Domain string `json:"domain"` - IdpEntityID *string `json:"idp_entity_id"` - IdpSsoURL *string `json:"idp_sso_url"` - IdpCertificate *string `json:"idp_certificate"` - AcsURL string `json:"acs_url"` - SPEntityID string `json:"sp_entity_id"` - SPMetadataURL string `json:"sp_metadata_url"` - Active bool `json:"active"` - Provider string `json:"provider"` - UserCount int64 `json:"user_count"` - SyncUserAttributes bool `json:"sync_user_attributes"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -type ListSAMLConnectionsResponse struct { - Data []SAMLConnection `json:"data"` - TotalCount int64 `json:"total_count"` -} - -type ListSAMLConnectionsParams struct { - Limit *int - Offset *int - Query *string - OrderBy *string -} - -func (s SAMLConnectionsService) ListAll(params ListSAMLConnectionsParams) (*ListSAMLConnectionsResponse, error) { - req, err := s.client.NewRequest(http.MethodGet, SAMLConnectionsUrl) - if err != nil { - return nil, err - } - - query := req.URL.Query() - addPaginationParams(query, PaginationParams{Limit: params.Limit, Offset: params.Offset}) - - if params.Query != nil { - query.Set("query", *params.Query) - } - if params.OrderBy != nil { - query.Set("order_by", *params.OrderBy) - } - - req.URL.RawQuery = query.Encode() - - samlConnections := &ListSAMLConnectionsResponse{} - if _, err = s.client.Do(req, samlConnections); err != nil { - return nil, err - } - - return samlConnections, nil -} - -func (s SAMLConnectionsService) Read(id string) (*SAMLConnection, error) { - req, err := s.client.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s", SAMLConnectionsUrl, id)) - if err != nil { - return nil, err - } - - samlConnection := &SAMLConnection{} - if _, err = s.client.Do(req, samlConnection); err != nil { - return nil, err - } - - return samlConnection, nil -} - -type CreateSAMLConnectionParams struct { - Name string `json:"name"` - Domain string `json:"domain"` - Provider string `json:"provider"` - IdpEntityID *string `json:"idp_entity_id,omitempty"` - IdpSsoURL *string `json:"idp_sso_url,omitempty"` - IdpCertificate *string `json:"idp_certificate,omitempty"` -} - -func (s SAMLConnectionsService) Create(params *CreateSAMLConnectionParams) (*SAMLConnection, error) { - req, err := s.client.NewRequest(http.MethodPost, SAMLConnectionsUrl, params) - if err != nil { - return nil, err - } - - resp := SAMLConnection{} - if _, err = s.client.Do(req, &resp); err != nil { - return nil, err - } - - return &resp, nil -} - -type UpdateSAMLConnectionParams struct { - Name *string `json:"name,omitempty"` - Domain *string `json:"domain,omitempty"` - IdpEntityID *string `json:"idp_entity_id,omitempty"` - IdpSsoURL *string `json:"idp_sso_url,omitempty"` - IdpCertificate *string `json:"idp_certificate,omitempty"` - Active *bool `json:"active,omitempty"` - SyncUserAttributes *bool `json:"sync_user_attributes,omitempty"` -} - -func (s SAMLConnectionsService) Update(id string, params *UpdateSAMLConnectionParams) (*SAMLConnection, error) { - req, err := s.client.NewRequest(http.MethodPatch, fmt.Sprintf("%s/%s", SAMLConnectionsUrl, id), params) - if err != nil { - return nil, err - } - - resp := SAMLConnection{} - if _, err = s.client.Do(req, &resp); err != nil { - return nil, err - } - - return &resp, nil -} - -func (s SAMLConnectionsService) Delete(id string) (*DeleteResponse, error) { - reqURL := fmt.Sprintf("%s/%s", SAMLConnectionsUrl, id) - req, err := s.client.NewRequest(http.MethodDelete, reqURL) - if err != nil { - return nil, err - } - - resp := &DeleteResponse{} - if _, err = s.client.Do(req, resp); err != nil { - return nil, err - } - return resp, nil -} diff --git a/clerk/saml_connections_test.go b/clerk/saml_connections_test.go deleted file mode 100644 index 388b9d0b..00000000 --- a/clerk/saml_connections_test.go +++ /dev/null @@ -1,214 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - "reflect" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestSAMLConnectionsService_ListAll(t *testing.T) { - c, mux, _, teardown := setup("token") - defer teardown() - - dummyResponse := fmt.Sprintf(`{ - "data": [%s], - "total_count": 1 - }`, dummySAMLConnectionJSON) - - mux.HandleFunc("/saml_connections", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodGet) - testHeader(t, req, "Authorization", "Bearer token") - - expectedQuery := url.Values{ - "limit": {"5"}, - "offset": {"6"}, - "query": {"my-query"}, - "order_by": {"created_at"}, - } - assert.Equal(t, expectedQuery, req.URL.Query()) - - _, _ = fmt.Fprint(w, dummyResponse) - }) - - listParams := ListSAMLConnectionsParams{ - Limit: intToPtr(5), - Offset: intToPtr(6), - Query: stringToPtr("my-query"), - OrderBy: stringToPtr("created_at"), - } - - got, err := c.SAMLConnections().ListAll(listParams) - assert.NoError(t, err) - - expected := &ListSAMLConnectionsResponse{} - _ = json.Unmarshal([]byte(dummyResponse), expected) - - if !reflect.DeepEqual(got, expected) { - t.Errorf("Response = %v, want %v", got, expected) - } -} - -func TestSAMLConnectionsService_Read(t *testing.T) { - dummyResponse := dummySAMLConnectionJSON - - c, mux, _, teardown := setup("token") - defer teardown() - - url := fmt.Sprintf("/saml_connections/%s", dummySAMLConnectionID) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodGet) - testHeader(t, req, "Authorization", "Bearer token") - _, _ = fmt.Fprint(w, dummyResponse) - }) - - got, err := c.SAMLConnections().Read(dummySAMLConnectionID) - assert.NoError(t, err) - - expected := SAMLConnection{} - _ = json.Unmarshal([]byte(dummyResponse), &expected) - - if !reflect.DeepEqual(*got, expected) { - t.Errorf("Response = %v, want %v", got, expected) - } -} - -func TestSAMLConnectionsService_Create(t *testing.T) { - dummyResponse := dummySAMLConnectionJSON - - c, mux, _, teardown := setup("token") - defer teardown() - - mux.HandleFunc("/saml_connections", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPost) - testHeader(t, req, "Authorization", "Bearer token") - _, _ = fmt.Fprint(w, dummyResponse) - }) - - createParams := &CreateSAMLConnectionParams{ - Name: "Testing SAML", - Domain: "example.com", - Provider: "saml_custom", - IdpEntityID: stringToPtr("test-idp-entity-id"), - IdpSsoURL: stringToPtr("https://example.com/saml/sso"), - IdpCertificate: stringToPtr(dummySAMLConnectionCertificate), - } - - got, err := c.SAMLConnections().Create(createParams) - assert.NoError(t, err) - - expected := SAMLConnection{} - _ = json.Unmarshal([]byte(dummyResponse), &expected) - - if !reflect.DeepEqual(*got, expected) { - t.Errorf("Response = %v, want %v", got, expected) - } -} - -func TestSAMLConnectionsService_Update(t *testing.T) { - expectedName := "New name for Testing SAML" - expectedActive := true - expectedSyncUserAttributes := false - dummyResponse := dummySAMLConnectionUpdatedJSON - - c, mux, _, teardown := setup("token") - defer teardown() - - url := fmt.Sprintf("/saml_connections/%s", dummySAMLConnectionID) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPatch) - testHeader(t, req, "Authorization", "Bearer token") - _, _ = fmt.Fprint(w, dummyResponse) - }) - - updateParams := &UpdateSAMLConnectionParams{ - Name: &expectedName, - Active: &expectedActive, - SyncUserAttributes: &expectedSyncUserAttributes, - } - - got, err := c.SAMLConnections().Update(dummySAMLConnectionID, updateParams) - assert.NoError(t, err) - - expected := SAMLConnection{} - _ = json.Unmarshal([]byte(dummyResponse), &expected) - - if !reflect.DeepEqual(*got, expected) { - t.Errorf("Response = %v, want %v", got, expected) - } -} - -func TestSAMLConnectionsService_Delete(t *testing.T) { - c, mux, _, teardown := setup("token") - defer teardown() - - url := fmt.Sprintf("/saml_connections/%s", dummySAMLConnectionID) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodDelete) - testHeader(t, req, "Authorization", "Bearer token") - response := fmt.Sprintf(`{ "deleted": true, "id": "%s", "object": "saml_connection" }`, dummySAMLConnectionID) - _, _ = fmt.Fprint(w, response) - }) - - expected := DeleteResponse{ - ID: dummySAMLConnectionID, - Object: "saml_connection", - Deleted: true, - } - - got, err := c.SAMLConnections().Delete(dummySAMLConnectionID) - assert.NoError(t, err) - - if !reflect.DeepEqual(*got, expected) { - t.Errorf("Response = %v, want %v", *got, expected) - } -} - -const ( - dummySAMLConnectionID = "samlc_2P17P4pXsx8MmunM1pkeYeimDDd" - - dummySAMLConnectionJSON = ` -{ - "object": "saml_connection", - "id": "` + dummySAMLConnectionID + `", - "name": "Testing SAML", - "domain": "example.com", - "idp_entity_id": "test-idp-entity-id", - "idp_sso_url": "https://example.com/saml/sso", - "idp_certificate": "` + dummySAMLConnectionCertificate + `", - "acs_url": "` + "https://clerk.example.com/v1/saml/acs/" + dummySAMLConnectionID + `", - "sp_entity_id": "` + "https://clerk.example.com/saml/" + dummySAMLConnectionID + `", - "sp_metadata_url": "` + "https://clerk.example.com/v1/saml/metadata/" + dummySAMLConnectionID + `", - "active": false, - "provider": "saml_custom", - "user_count": 3, - "sync_user_attributes": true -}` - - dummySAMLConnectionUpdatedJSON = ` -{ - "object": "saml_connection", - "id": "` + dummySAMLConnectionID + `", - "name": "New name for Testing SAML", - "domain": "example.com", - "idp_entity_id": "test-idp-entity-id", - "idp_sso_url": "https://example.com/saml/sso", - "idp_certificate": "` + dummySAMLConnectionCertificate + `", - "acs_url": "` + "https://clerk.example.com/v1/saml/acs/" + dummySAMLConnectionID + `", - "sp_entity_id": "` + "https://clerk.example.com/saml/" + dummySAMLConnectionID + `", - "sp_metadata_url": "` + "https://clerk.example.com/v1/saml/metadata/" + dummySAMLConnectionID + `", - "active": true, - "provider": "saml_custom", - "user_count": 3, - "sync_user_attributes": false -}` - - dummySAMLConnectionCertificate = `MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=` -) diff --git a/clerk/session_claims.go b/clerk/session_claims.go deleted file mode 100644 index d9680c0a..00000000 --- a/clerk/session_claims.go +++ /dev/null @@ -1,38 +0,0 @@ -package clerk - -import ( - "encoding/json" - - "github.com/go-jose/go-jose/v3/jwt" -) - -type SessionClaims struct { - jwt.Claims - SessionID string `json:"sid"` - AuthorizedParty string `json:"azp"` - ActiveOrganizationID string `json:"org_id"` - ActiveOrganizationSlug string `json:"org_slug"` - ActiveOrganizationRole string `json:"org_role"` - ActiveOrganizationPermissions []string `json:"org_permissions"` - Actor json.RawMessage `json:"act,omitempty"` -} - -// HasPermission checks if the user has the specific permission -// in their session claims. -func (s *SessionClaims) HasPermission(permission string) bool { - for _, sessPermission := range s.ActiveOrganizationPermissions { - if sessPermission == permission { - return true - } - } - return false -} - -// HasRole checks if the user has the specific role -// in their session claims. -// Performing role checks is not considered a best-practice and -// developers should avoid it as much as possible. -// Usually, complex role checks can be refactored with a single permission check. -func (s *SessionClaims) HasRole(role string) bool { - return s.ActiveOrganizationRole == role -} diff --git a/clerk/session_claims_test.go b/clerk/session_claims_test.go deleted file mode 100644 index d3ebb6b1..00000000 --- a/clerk/session_claims_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package clerk - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestSessionClaims_HasPermissiont(t *testing.T) { - // user has permission - hasPermission := dummySessionClaims.HasPermission("org:billing:manage") - assert.True(t, hasPermission) - - // user has second permission - hasPermission = dummySessionClaims.HasPermission("org:report:view") - assert.True(t, hasPermission) - - // user does not have permission - hasPermission = dummySessionClaims.HasPermission("org:billing:create") - assert.False(t, hasPermission) -} - -func TestSessionClaims_HasRole(t *testing.T) { - // user has role - hasRole := dummySessionClaims.HasRole("org_role") - assert.True(t, hasRole) - - // user does not have role - hasRole = dummySessionClaims.HasRole("org_role_nonexistent") - assert.False(t, hasRole) -} diff --git a/clerk/sessions.go b/clerk/sessions.go deleted file mode 100644 index 71e83df7..00000000 --- a/clerk/sessions.go +++ /dev/null @@ -1,115 +0,0 @@ -package clerk - -import "fmt" - -type SessionsService service - -type Session struct { - Object string `json:"object"` - ID string `json:"id"` - ClientID string `json:"client_id"` - UserID string `json:"user_id"` - Status string `json:"status"` - LastActiveAt int64 `json:"last_active_at"` - LastActiveOrganizationID string `json:"last_active_organization_id,omitempty"` - ExpireAt int64 `json:"expire_at"` - AbandonAt int64 `json:"abandon_at"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -type ListAllSessionsParams struct { - Limit *int - Offset *int - ClientID *string - UserID *string - Status *SessionStatus -} - -type SessionStatus string - -const ( - SessionStatusAbandoned SessionStatus = "abandoned" - SessionStatusActive SessionStatus = "active" - SessionStatusEnded SessionStatus = "ended" - SessionStatusExpired SessionStatus = "expired" - SessionStatusRemoved SessionStatus = "removed" - SessionStatusReplaced SessionStatus = "replaced" - SessionStatusRevoked SessionStatus = "revoked" -) - -func (s *SessionsService) ListAll() ([]Session, error) { - sessionsUrl := "sessions" - req, _ := s.client.NewRequest("GET", sessionsUrl) - - var sessions []Session - _, err := s.client.Do(req, &sessions) - if err != nil { - return nil, err - } - return sessions, nil -} - -func (s *SessionsService) ListAllWithFiltering(params ListAllSessionsParams) ([]Session, error) { - sessionsUrl := "sessions" - req, _ := s.client.NewRequest("GET", sessionsUrl) - - paginationParams := PaginationParams{Limit: params.Limit, Offset: params.Offset} - query := req.URL.Query() - addPaginationParams(query, paginationParams) - - if params.ClientID != nil { - query.Add("client_id", *params.ClientID) - } - if params.UserID != nil { - query.Add("user_id", *params.UserID) - } - if params.Status != nil { - status := string(*params.Status) - query.Add("status", status) - } - - req.URL.RawQuery = query.Encode() - - var sessions []Session - _, err := s.client.Do(req, &sessions) - if err != nil { - return nil, err - } - return sessions, nil -} - -func (s *SessionsService) Read(sessionId string) (*Session, error) { - sessionUrl := fmt.Sprintf("%s/%s", SessionsUrl, sessionId) - req, _ := s.client.NewRequest("GET", sessionUrl) - - var session Session - _, err := s.client.Do(req, &session) - if err != nil { - return nil, err - } - return &session, nil -} - -func (s *SessionsService) Revoke(sessionId string) (*Session, error) { - sessionUrl := fmt.Sprintf("%s/%s/revoke", SessionsUrl, sessionId) - req, _ := s.client.NewRequest("POST", sessionUrl) - - var session Session - _, err := s.client.Do(req, &session) - if err != nil { - return nil, err - } - return &session, nil -} - -func (s *SessionsService) Verify(sessionId, token string) (*Session, error) { - verifyUrl := fmt.Sprintf("%s/%s/verify", SessionsUrl, sessionId) - var sessionResponse Session - - err := doVerify(s.client, verifyUrl, token, &sessionResponse) - if err != nil { - return nil, err - } - return &sessionResponse, nil -} diff --git a/clerk/sessions_test.go b/clerk/sessions_test.go deleted file mode 100644 index 7314463f..00000000 --- a/clerk/sessions_test.go +++ /dev/null @@ -1,254 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - "reflect" - "testing" -) - -func TestSessionsService_ListAll_happyPath_noParams(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := "[" + dummySessionJson + "]" - - mux.HandleFunc("/sessions", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - fmt.Fprint(w, expectedResponse) - }) - - var want []Session - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Sessions().ListAll() - if len(got) != len(want) { - t.Errorf("Was expecting %d sessions to be returned, instead got %d", len(want), len(got)) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestSessionsService_ListAll_pagination_and_filtering_params(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := "[" + dummySessionJson + "]" - - mux.HandleFunc("/sessions", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - - queryParams := url.Values{ - "limit": {}, - "offset": {}, - "client_id": {}, - "user_id": {}, - "status": {}, - } - - testQuery(t, req, queryParams) - fmt.Fprint(w, expectedResponse) - }) - - var want []Session - _ = json.Unmarshal([]byte(expectedResponse), &want) - - limit := 2 - offset := 2 - status := SessionStatusEnded - userId := "user_1mebQggrD3xO5JfuHk7clQ94ysA" - clientId := "client_1mebPYz8NFNA17fi7NemNXIwp1p" - - got, _ := client.Sessions().ListAllWithFiltering(ListAllSessionsParams{ - Limit: &limit, - Offset: &offset, - Status: &status, - UserID: &userId, - ClientID: &clientId, - }) - - if len(got) != len(want) { - t.Errorf("Was expecting %d sessions to be returned, instead got %d", len(want), len(got)) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestSessionsService_ListAll_pagination_and_filtering_empty_params(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := "[" + dummySessionJson + "]" - - mux.HandleFunc("/sessions", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - - queryParams := url.Values{} - - testQuery(t, req, queryParams) - fmt.Fprint(w, expectedResponse) - }) - - var want []Session - _ = json.Unmarshal([]byte(expectedResponse), &want) - - limit := 2 - offset := 2 - status := SessionStatusEnded - userId := "user_1mebQggrD3xO5JfuHk7clQ94ysA" - clientId := "client_1mebPYz8NFNA17fi7NemNXIwp1p" - - got, _ := client.Sessions().ListAllWithFiltering(ListAllSessionsParams{ - Limit: &limit, - Offset: &offset, - Status: &status, - UserID: &userId, - ClientID: &clientId, - }) - - if len(got) != len(want) { - t.Errorf("Was expecting %d sessions to be returned, instead got %d", len(want), len(got)) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestSessionsService_ListAll_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - sessions, err := client.Sessions().ListAll() - if err == nil { - t.Errorf("Expected error to be returned") - } - if sessions != nil { - t.Errorf("Was not expecting any sessions to be returned, instead got %v", sessions) - } -} - -func TestSessionsService_Read_happyPath(t *testing.T) { - token := "token" - sessionId := "someSessionId" - expectedResponse := dummySessionJson - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/sessions/"+sessionId, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want Session - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Sessions().Read(sessionId) - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -func TestSessionsService_Read_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - session, err := client.Sessions().Read("someSessionId") - if err == nil { - t.Errorf("Expected error to be returned") - } - if session != nil { - t.Errorf("Was not expecting any session to be returned, instead got %v", session) - } -} - -func TestSessionsService_Revoke_happyPath(t *testing.T) { - token := "token" - sessionId := "someSessionId" - expectedResponse := dummySessionJson - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/sessions/"+sessionId+"/revoke", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want Session - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Sessions().Revoke(sessionId) - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -func TestSessionsService_Revoke_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - session, err := client.Sessions().Revoke("someSessionId") - if err == nil { - t.Errorf("Expected error to be returned") - } - if session != nil { - t.Errorf("Was not expecting any session to be returned, instead got %v", session) - } -} - -func TestSessionsService_Verify_happyPath(t *testing.T) { - token := "token" - sessionId := "someSessionId" - sessionToken := "someSessionToken" - expectedResponse := dummySessionJson - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/sessions/"+sessionId+"/verify", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want Session - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Sessions().Verify(sessionId, sessionToken) - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -func TestSessionsService_Verify_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - session, err := client.Sessions().Verify("someSessionId", "someSessionToken") - if err == nil { - t.Errorf("Expected error to be returned") - } - if session != nil { - t.Errorf("Was not expecting any session to be returned, instead got %v", session) - } -} - -const dummySessionJson = `{ - "abandon_at": 1612448988, - "client_id": "client_1mebPYz8NFNA17fi7NemNXIwp1p", - "expire_at": 1610461788, - "id": "sess_1mebQdHlQI14cjxln4e2eXNzwzi", - "last_active_at": 1609857251, - "object": "session", - "status": "ended", - "user_id": "user_1mebQggrD3xO5JfuHk7clQ94ysA" - }` diff --git a/clerk/templates.go b/clerk/templates.go deleted file mode 100644 index d990d4ce..00000000 --- a/clerk/templates.go +++ /dev/null @@ -1,154 +0,0 @@ -package clerk - -import "fmt" - -type TemplatesService service - -type Template struct { - Object string `json:"object"` - Slug string `json:"slug"` - ResourceType string `json:"resource_type"` - TemplateType string `json:"template_type"` - Name string `json:"name"` - Position int `json:"position"` - CanRevert bool `json:"can_revert"` - CanDelete bool `json:"can_delete"` - FromEmailName *string `json:"from_email_name"` - DeliveredByClerk bool `json:"delivered_by_clerk"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -type TemplateExtended struct { - *Template - Subject string `json:"subject"` - Markup string `json:"markup"` - Body string `json:"body"` - AvailableVariables []string `json:"available_variables"` - RequiredVariables []string `json:"required_variables"` -} - -type TemplatePreview struct { - Subject string `json:"subject,omitempty"` - Body string `json:"body"` - FromEmailAddress *string `json:"from_email_address,omitempty"` -} - -func (s *TemplatesService) ListAll(templateType string) ([]Template, error) { - templateURL := fmt.Sprintf("%s/%s", TemplatesUrl, templateType) - req, _ := s.client.NewRequest("GET", templateURL) - - var templates []Template - - _, err := s.client.Do(req, &templates) - if err != nil { - return nil, err - } - - return templates, nil -} - -func (s *TemplatesService) Read(templateType, slug string) (*TemplateExtended, error) { - templateURL := fmt.Sprintf("%s/%s/%s", TemplatesUrl, templateType, slug) - - req, _ := s.client.NewRequest("GET", templateURL) - - var templateExtended TemplateExtended - - _, err := s.client.Do(req, &templateExtended) - if err != nil { - return nil, err - } - - return &templateExtended, nil -} - -type UpsertTemplateRequest struct { - Name string `json:"name"` - Subject string `json:"subject,omitempty"` - Markup string `json:"markup,omitempty"` - Body string `json:"body"` - FromEmailName *string `json:"from_email_name"` - DeliveredByClerk *bool `json:"delivered_by_clerk"` -} - -type PreviewTemplateRequest struct { - Subject string `json:"subject,omitempty"` - Body string `json:"body"` - FromEmailName *string `json:"from_email_name"` -} - -type ToggleDeliveryTemplateRequest struct { - DeliveredByClerk bool `json:"delivered_by_clerk"` -} - -func (s *TemplatesService) Upsert(templateType, slug string, upsertTemplateRequest *UpsertTemplateRequest) (*TemplateExtended, error) { - templateURL := fmt.Sprintf("%s/%s/%s", TemplatesUrl, templateType, slug) - req, _ := s.client.NewRequest("PUT", templateURL, upsertTemplateRequest) - - var upsertedTemplate TemplateExtended - - _, err := s.client.Do(req, &upsertedTemplate) - if err != nil { - return nil, err - } - - return &upsertedTemplate, nil -} - -// Revert reverts a user template to the corresponding system template -func (s *TemplatesService) Revert(templateType, slug string) (*TemplateExtended, error) { - templateURL := fmt.Sprintf("%s/%s/%s/revert", TemplatesUrl, templateType, slug) - req, _ := s.client.NewRequest("POST", templateURL) - - var templateExtended TemplateExtended - - _, err := s.client.Do(req, &templateExtended) - if err != nil { - return nil, err - } - - return &templateExtended, nil -} - -// Delete deletes a custom user template -func (s *TemplatesService) Delete(templateType, slug string) (*DeleteResponse, error) { - templateURL := fmt.Sprintf("%s/%s/%s", TemplatesUrl, templateType, slug) - req, _ := s.client.NewRequest("DELETE", templateURL) - - var delResponse DeleteResponse - if _, err := s.client.Do(req, &delResponse); err != nil { - return nil, err - } - - return &delResponse, nil -} - -// Preview returns a rendering of a template with sample data for preview purposes -func (s *TemplatesService) Preview(templateType, slug string, previewTemplateRequest *PreviewTemplateRequest) (*TemplatePreview, error) { - templateURL := fmt.Sprintf("%s/%s/%s/preview", TemplatesUrl, templateType, slug) - req, _ := s.client.NewRequest("POST", templateURL, previewTemplateRequest) - - var templatePreview TemplatePreview - - _, err := s.client.Do(req, &templatePreview) - if err != nil { - return nil, err - } - - return &templatePreview, nil -} - -func (s *TemplatesService) ToggleDelivery(templateType, slug string, toggleDeliveryTemplateRequest *ToggleDeliveryTemplateRequest) (*TemplateExtended, error) { - templateURL := fmt.Sprintf("%s/%s/%s/toggle_delivery", TemplatesUrl, templateType, slug) - req, _ := s.client.NewRequest("POST", templateURL, toggleDeliveryTemplateRequest) - - var toggledTemplate TemplateExtended - - _, err := s.client.Do(req, &toggledTemplate) - if err != nil { - return nil, err - } - - return &toggledTemplate, nil -} diff --git a/clerk/templates_test.go b/clerk/templates_test.go deleted file mode 100644 index 758540d5..00000000 --- a/clerk/templates_test.go +++ /dev/null @@ -1,227 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "reflect" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestTemplatesService_List_All_happyPath(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - templateType := "email" - - expectedResponse := "[" + dummyTemplateJSON + "]" - - url := fmt.Sprintf("/templates/%s", templateType) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - fmt.Fprint(w, expectedResponse) - }) - - var want []Template - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, err := client.Templates().ListAll(templateType) - assert.Nil(t, err) - - if len(got) != len(want) { - t.Errorf("Was expecting %d user to be returned, instead got %d", len(want), len(got)) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestTemplatesService_Read_happyPath(t *testing.T) { - token := "token" - templateType := "email" - slug := "metalslug" - expectedResponse := dummyTemplateJSON - - client, mux, _, teardown := setup(token) - defer teardown() - - url := fmt.Sprintf("/templates/%s/%s", templateType, slug) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want TemplateExtended - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, err := client.Templates().Read(templateType, slug) - assert.Nil(t, err) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -func TestTemplatesService_Upsert_happyPath(t *testing.T) { - token := "token" - templateType := "email" - slug := "metalslug" - expectedResponse := dummyTemplateJSON - - var payload UpsertTemplateRequest - _ = json.Unmarshal([]byte(dummyUpsertRequestJSON), &payload) - - client, mux, _, teardown := setup(token) - defer teardown() - - url := fmt.Sprintf("/templates/%s/%s", templateType, slug) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "PUT") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - got, err := client.Templates().Upsert(templateType, slug, &payload) - assert.Nil(t, err) - - var want TemplateExtended - _ = json.Unmarshal([]byte(expectedResponse), &want) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, payload) - } -} - -func TestTemplatesService_RevertToSystemTemplate_happyPath(t *testing.T) { - token := "token" - templateType := "email" - slug := "metalslug" - expectedResponse := dummyTemplateJSON - - client, mux, _, teardown := setup(token) - defer teardown() - - url := fmt.Sprintf("/templates/%s/%s/revert", templateType, slug) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want TemplateExtended - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, err := client.Templates().Revert(templateType, slug) - assert.Nil(t, err) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -func TestTemplatesService_Delete_happyPath(t *testing.T) { - token := "token" - templateType := "email" - slug := "metalslug" - - client, mux, _, teardown := setup(token) - defer teardown() - - url := fmt.Sprintf("/templates/%s/%s", templateType, slug) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "DELETE") - testHeader(t, req, "Authorization", "Bearer "+token) - response := fmt.Sprintf(`{ "deleted": true, "slug": "%v", "object": "template" }`, slug) - fmt.Fprint(w, response) - }) - - want := DeleteResponse{Slug: slug, Object: "template", Deleted: true} - - got, err := client.Templates().Delete(templateType, slug) - assert.Nil(t, err) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -func TestTemplatesService_Preview_happyPath(t *testing.T) { - token := "token" - templateType := "sms" - slug := "snail" - expectedResponse := dummyPreviewResponseJSON - - var payload PreviewTemplateRequest - _ = json.Unmarshal([]byte(dummyPreviewRequestJSON), &payload) - - client, mux, _, teardown := setup(token) - defer teardown() - - url := fmt.Sprintf("/templates/%s/%s/preview", templateType, slug) - - mux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - got, err := client.Templates().Preview(templateType, slug, &payload) - assert.Nil(t, err) - - var want TemplatePreview - _ = json.Unmarshal([]byte(expectedResponse), &want) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, payload) - } -} - -const dummyTemplateJSON = `{ - "object": "template", - "slug": "derp", - "resource_type": "user", - "template_type": "email", - "name": "Vin Diesel", - "position": 0, - "created_at": 1633541368454, - "updated_at": 1633541368454, - "subject": "Choo choo train", - "markup": "
Hee Hee
", - "body": "Ho Ho
", - "from_email_name": "noreply", - "delivered_by_clerk": true, - "available_variables": [ - "michael", - "jackson" - ], - "required_variables": [ - "michael" - ] -}` - -const dummyUpsertRequestJSON = `{ - "name": "Dominic Toretto", - "subject": "NOS bottles for sale", - "markup": "Family
", - "body": "One quarter of a mile at a time
", - "from_email_name": "sales", - "delivered_by_clerk": false -}` - -const dummyPreviewRequestJSON = `{ - "body": "{{OTPCode}} is your code for {{AppName}}, valid for {{TTLMinutes}} minutes" -}` - -const dummyPreviewResponseJSON = `{ - "body": "123456 is your code for ACME, valid for 10 minutes" -}` diff --git a/clerk/tokens.go b/clerk/tokens.go deleted file mode 100644 index fc5fa2c0..00000000 --- a/clerk/tokens.go +++ /dev/null @@ -1,129 +0,0 @@ -package clerk - -import ( - "fmt" - "time" - - "github.com/go-jose/go-jose/v3" - "github.com/go-jose/go-jose/v3/jwt" -) - -var standardClaimsKeys = []string{"iss", "sub", "aud", "exp", "nbf", "iat", "jti"} - -type TokenClaims struct { - jwt.Claims - Extra map[string]interface{} -} - -// DecodeToken decodes a jwt token without verifying it. -func (c *client) DecodeToken(token string) (*TokenClaims, error) { - parsedToken, err := jwt.ParseSigned(token) - if err != nil { - return nil, err - } - - standardClaims := jwt.Claims{} - extraClaims := make(map[string]interface{}) - - if err = parsedToken.UnsafeClaimsWithoutVerification(&standardClaims, &extraClaims); err != nil { - return nil, err - } - - // Delete any standard claims included in the extra claims - for _, key := range standardClaimsKeys { - delete(extraClaims, key) - } - - return &TokenClaims{Claims: standardClaims, Extra: extraClaims}, nil -} - -type verifyTokenOptions struct { - authorizedParties map[string]struct{} - leeway time.Duration - jwk *jose.JSONWebKey - customClaims interface{} - isSatellite bool - proxyURL string -} - -// VerifyToken verifies the session jwt token. -func (c *client) VerifyToken(token string, opts ...VerifyTokenOption) (*SessionClaims, error) { - options := &verifyTokenOptions{} - - for _, opt := range opts { - if err := opt(options); err != nil { - return nil, err - } - } - - parsedToken, err := jwt.ParseSigned(token) - if err != nil { - return nil, err - } - - if len(parsedToken.Headers) == 0 { - return nil, fmt.Errorf("missing jwt headers") - } - - kid := parsedToken.Headers[0].KeyID - if kid == "" { - return nil, fmt.Errorf("missing jwt kid header claim") - } - - jwk := options.jwk - if jwk == nil { - jwk, err = c.getJWK(kid) - if err != nil { - return nil, err - } - } - - if parsedToken.Headers[0].Algorithm != jwk.Algorithm { - return nil, fmt.Errorf("invalid signing algorithm %s", jwk.Algorithm) - } - - claims := SessionClaims{} - if err = verifyTokenParseClaims(parsedToken, jwk.Key, &claims, options); err != nil { - return nil, err - } - - if err = claims.Claims.ValidateWithLeeway(jwt.Expected{Time: time.Now()}, options.leeway); err != nil { - return nil, err - } - - issuer := newIssuer(claims.Issuer). - WithSatelliteDomain(options.isSatellite). - WithProxyURL(options.proxyURL) - - if !issuer.IsValid() { - return nil, fmt.Errorf("invalid issuer %s", claims.Issuer) - } - - if claims.AuthorizedParty != "" && len(options.authorizedParties) > 0 { - if _, ok := options.authorizedParties[claims.AuthorizedParty]; !ok { - return nil, fmt.Errorf("invalid authorized party %s", claims.AuthorizedParty) - } - } - - return &claims, nil -} - -func (c *client) getJWK(kid string) (*jose.JSONWebKey, error) { - if c.jwksCache.isInvalid() { - jwks, err := c.jwks.ListAll() - if err != nil { - return nil, err - } - - c.jwksCache.set(jwks) - } - - return c.jwksCache.get(kid) -} - -func verifyTokenParseClaims(parsedToken *jwt.JSONWebToken, key interface{}, sessionClaims *SessionClaims, options *verifyTokenOptions) error { - if options.customClaims == nil { - return parsedToken.Claims(key, sessionClaims) - } - return parsedToken.Claims(key, sessionClaims, options.customClaims) -} diff --git a/clerk/tokens_issuer.go b/clerk/tokens_issuer.go deleted file mode 100644 index a6a96c3e..00000000 --- a/clerk/tokens_issuer.go +++ /dev/null @@ -1,38 +0,0 @@ -package clerk - -import "strings" - -type issuer struct { - iss string - isSatellite bool - proxyURL string -} - -func newIssuer(iss string) *issuer { - return &issuer{ - iss: iss, - } -} - -func (iss *issuer) WithSatelliteDomain(isSatellite bool) *issuer { - iss.isSatellite = isSatellite - return iss -} - -func (iss *issuer) WithProxyURL(proxyURL string) *issuer { - iss.proxyURL = proxyURL - return iss -} - -func (iss *issuer) IsValid() bool { - if iss.isSatellite { - return true - } - - if iss.proxyURL != "" { - return iss.iss == iss.proxyURL - } - - return strings.HasPrefix(iss.iss, "https://clerk.") || - strings.Contains(iss.iss, ".clerk.accounts") -} diff --git a/clerk/tokens_options.go b/clerk/tokens_options.go deleted file mode 100644 index c2692f6e..00000000 --- a/clerk/tokens_options.go +++ /dev/null @@ -1,96 +0,0 @@ -package clerk - -import ( - "crypto/x509" - "encoding/pem" - "fmt" - "strings" - "time" - - "github.com/go-jose/go-jose/v3" -) - -// VerifyTokenOption describes a functional parameter for the VerifyToken method -type VerifyTokenOption func(*verifyTokenOptions) error - -// WithAuthorizedParty allows to set the authorized parties to check against the azp claim of the session token -func WithAuthorizedParty(parties ...string) VerifyTokenOption { - return func(o *verifyTokenOptions) error { - authorizedParties := make(map[string]struct{}) - for _, party := range parties { - authorizedParties[party] = struct{}{} - } - - o.authorizedParties = authorizedParties - return nil - } -} - -// WithLeeway allows to set a custom leeway that gives some extra time to the token to accomodate for clock skew, etc. -func WithLeeway(leeway time.Duration) VerifyTokenOption { - return func(o *verifyTokenOptions) error { - o.leeway = leeway - return nil - } -} - -// WithJWTVerificationKey allows to set the JWK to use for verifying tokens without the need to download or cache any JWKs at runtime -func WithJWTVerificationKey(key string) VerifyTokenOption { - return func(o *verifyTokenOptions) error { - // From the Clerk docs: "Note that the JWT Verification key is not in - // PEM format, the header and footer are missing, in order to be shorter - // and single-line for easier setup." - if !strings.HasPrefix(key, "-----BEGIN") { - key = "-----BEGIN PUBLIC KEY-----\n" + key + "\n-----END PUBLIC KEY-----" - } - - jwk, err := pemToJWK(key) - if err != nil { - return err - } - - o.jwk = jwk - return nil - } -} - -// WithCustomClaims allows to pass a type (e.g. struct), which will be populated with the token claims based on json tags. -// For this option to work you must pass a pointer. -func WithCustomClaims(customClaims interface{}) VerifyTokenOption { - return func(o *verifyTokenOptions) error { - o.customClaims = customClaims - return nil - } -} - -func WithSatelliteDomain(isSatellite bool) VerifyTokenOption { - return func(o *verifyTokenOptions) error { - o.isSatellite = isSatellite - return nil - } -} - -func WithProxyURL(proxyURL string) VerifyTokenOption { - return func(o *verifyTokenOptions) error { - o.proxyURL = proxyURL - return nil - } -} - -func pemToJWK(key string) (*jose.JSONWebKey, error) { - block, _ := pem.Decode([]byte(key)) - if block == nil { - return nil, fmt.Errorf("invalid PEM-encoded block") - } - - if block.Type != "PUBLIC KEY" { - return nil, fmt.Errorf("invalid key type, expected a public key") - } - - rsaPublicKey, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return nil, fmt.Errorf("failed to parse public key: %v", err) - } - - return &jose.JSONWebKey{Key: rsaPublicKey, Algorithm: "RS256"}, nil -} diff --git a/clerk/tokens_options_test.go b/clerk/tokens_options_test.go deleted file mode 100644 index 66d91472..00000000 --- a/clerk/tokens_options_test.go +++ /dev/null @@ -1,110 +0,0 @@ -package clerk - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestWithAuthorizedPartyNone(t *testing.T) { - opts := &verifyTokenOptions{} - err := WithAuthorizedParty()(opts) - - if assert.NoError(t, err) { - assert.Len(t, opts.authorizedParties, 0) - } -} - -func TestWithAuthorizedPartySingle(t *testing.T) { - opts := &verifyTokenOptions{} - err := WithAuthorizedParty("test-party")(opts) - - assert.NoError(t, err) - assert.Len(t, opts.authorizedParties, 1) - assert.Equal(t, arrayToMap(t, []string{"test-party"}), opts.authorizedParties) -} - -func TestWithAuthorizedPartyMultiple(t *testing.T) { - authorizedParties := []string{"test-party", "another_party", "yet-another-party"} - - opts := &verifyTokenOptions{} - err := WithAuthorizedParty(authorizedParties...)(opts) - - assert.NoError(t, err) - assert.Len(t, opts.authorizedParties, len(authorizedParties)) - assert.Equal(t, arrayToMap(t, authorizedParties), opts.authorizedParties) -} - -func TestWithLeeway(t *testing.T) { - leeway := 5 * time.Second - - opts := &verifyTokenOptions{} - err := WithLeeway(leeway)(opts) - - assert.NoError(t, err) - assert.Equal(t, opts.leeway, leeway) -} - -func TestWithJWTVerificationKey(t *testing.T) { - key := "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm7Zs5PFGrsrmvys1hHkSDOYoghz9+z9o+E6WgMqR+R/Af0/QRqQo/YwCmzB+01+5Us1NdSa32YuQYiMxV4T+g3eebSiBqPNiCyjl2wttCm5LAV5iHyVqwnBNcrXlA5mRFQz8lmyfpoksNDEVzJPwwHzPjKSIKsGgsrPnw6XsyOPJY/8UocscEcHptTmahHrbfNZLN0FrMneHw9tnn2AiUctuU9bw80KwPd+WFdZ6UZF/kPxVFsANJpz1aMpz7Lxi3Sz1ztUCdHvNJitRUO1Qewby4xi9DfIEECMq78LLmwGaTiKxutC6KwHLJEcbUblOJHpYVEXdBex9xGJ/2DHrBQIDAQAB" - - opts := &verifyTokenOptions{} - err := WithJWTVerificationKey(key)(opts) - - if assert.NoError(t, err) { - assert.Equal(t, "RS256", opts.jwk.Algorithm) - } -} - -func TestWithJWTVerificationKey_PEM(t *testing.T) { - key := `-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm7Zs5PFGrsrmvys1hHkS -DOYoghz9+z9o+E6WgMqR+R/Af0/QRqQo/YwCmzB+01+5Us1NdSa32YuQYiMxV4T+ -g3eebSiBqPNiCyjl2wttCm5LAV5iHyVqwnBNcrXlA5mRFQz8lmyfpoksNDEVzJPw -wHzPjKSIKsGgsrPnw6XsyOPJY/8UocscEcHptTmahHrbfNZLN0FrMneHw9tnn2Ai -UctuU9bw80KwPd+WFdZ6UZF/kPxVFsANJpz1aMpz7Lxi3Sz1ztUCdHvNJitRUO1Q -ewby4xi9DfIEECMq78LLmwGaTiKxutC6KwHLJEcbUblOJHpYVEXdBex9xGJ/2DHr -BQIDAQAB ------END PUBLIC KEY-----` - - opts := &verifyTokenOptions{} - err := WithJWTVerificationKey(key)(opts) - - if assert.NoError(t, err) { - assert.Equal(t, "RS256", opts.jwk.Algorithm) - } -} - -func TestWithSatelliteDomain(t *testing.T) { - isSatellite := true - - opts := &verifyTokenOptions{} - err := WithSatelliteDomain(isSatellite)(opts) - - if assert.NoError(t, err) { - assert.Equal(t, isSatellite, opts.isSatellite) - } -} - -func TestWithProxyURL(t *testing.T) { - proxyURL := "url" - - opts := &verifyTokenOptions{} - err := WithProxyURL(proxyURL)(opts) - - if assert.NoError(t, err) { - assert.Equal(t, proxyURL, opts.proxyURL) - } -} - -func arrayToMap(t *testing.T, input []string) map[string]struct{} { - t.Helper() - - output := make(map[string]struct{}) - for _, s := range input { - output[s] = struct{}{} - } - - return output -} diff --git a/clerk/tokens_test.go b/clerk/tokens_test.go deleted file mode 100644 index c4928dba..00000000 --- a/clerk/tokens_test.go +++ /dev/null @@ -1,447 +0,0 @@ -package clerk - -import ( - "crypto" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/json" - "encoding/pem" - "net/http" - "reflect" - "testing" - "time" - - "github.com/go-jose/go-jose/v3" - "github.com/go-jose/go-jose/v3/jwt" - "github.com/stretchr/testify/assert" -) - -var ( - dummyTokenClaims = map[string]interface{}{ - "iss": "issuer", - "sub": "subject", - "aud": []string{"clerk"}, - "name": "name", - "picture": "picture", - } - - dummyTokenClaimsExpected = TokenClaims{ - Claims: jwt.Claims{ - Issuer: "issuer", - Subject: "subject", - Audience: jwt.Audience{"clerk"}, - Expiry: nil, - IssuedAt: nil, - }, - Extra: map[string]interface{}{ - "name": "name", - "picture": "picture", - }, - } - - dummySessionClaims = SessionClaims{ - Claims: jwt.Claims{ - Issuer: "https://clerk.issuer", - Subject: "subject", - Audience: nil, - Expiry: nil, - IssuedAt: nil, - }, - SessionID: "session_id", - AuthorizedParty: "authorized_party", - ActiveOrganizationID: "org_id", - ActiveOrganizationSlug: "org_slug", - ActiveOrganizationRole: "org_role", - ActiveOrganizationPermissions: []string{"org:billing:manage", "org:report:view"}, - } -) - -func TestClient_DecodeToken_EmptyToken(t *testing.T) { - c, _ := NewClient("token") - - _, err := c.DecodeToken("") - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestClient_DecodeToken_Success(t *testing.T) { - c, _ := NewClient("token") - token, _ := testGenerateTokenJWT(t, dummyTokenClaims, "kid") - - got, _ := c.DecodeToken(token) - - if !reflect.DeepEqual(got, &dummyTokenClaimsExpected) { - t.Errorf("Expected %+v, but got %+v", &dummyTokenClaimsExpected, got) - } -} - -func TestClient_VerifyToken_EmptyToken(t *testing.T) { - c, _ := NewClient("token") - - _, err := c.VerifyToken("") - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestClient_VerifyToken_MissingKID(t *testing.T) { - c, _ := NewClient("token") - token, _ := testGenerateTokenJWT(t, dummySessionClaims, "") - - _, err := c.VerifyToken(token) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestClient_VerifyToken_MismatchKID(t *testing.T) { - c, _ := NewClient("token") - token, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "invalid-kid")) - - _, err := c.VerifyToken(token) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestClient_VerifyToken_MismatchAlgorithm(t *testing.T) { - c, _ := NewClient("token") - token, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS512, "kid")) - - _, err := c.VerifyToken(token) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestClient_VerifyToken_InvalidKey(t *testing.T) { - c, _ := NewClient("token") - token, _ := testGenerateTokenJWT(t, dummySessionClaims, "kid") - privKey, _ := rsa.GenerateKey(rand.Reader, 2048) - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, privKey.Public(), jose.RS256, "kid")) - - _, err := c.VerifyToken(token) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestClient_VerifyToken_InvalidIssuer(t *testing.T) { - c, _ := NewClient("token") - - claims := dummySessionClaims - claims.Issuer = "issuer" - - token, pubKey := testGenerateTokenJWT(t, claims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - - _, err := c.VerifyToken(token) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestClient_VerifyToken_IssuerSatelliteDomain(t *testing.T) { - c, _ := NewClient("token") - - token, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - - got, _ := c.VerifyToken(token, WithSatelliteDomain(true)) - if !reflect.DeepEqual(got, &dummySessionClaims) { - t.Errorf("Expected %+v, but got %+v", dummySessionClaims, got) - } -} - -func TestClient_VerifyToken_InvalidIssuerProxyURL(t *testing.T) { - c, _ := NewClient("token") - - claims := dummySessionClaims - claims.Issuer = "invalid" - - token, pubKey := testGenerateTokenJWT(t, claims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - - _, err := c.VerifyToken(token, WithProxyURL("issuer")) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestClient_VerifyToken_ValidIssuerProxyURL(t *testing.T) { - c, _ := NewClient("token") - - claims := dummySessionClaims - claims.Issuer = "issuer" - - token, pubKey := testGenerateTokenJWT(t, claims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - - got, _ := c.VerifyToken(token, WithProxyURL("issuer")) - if !reflect.DeepEqual(got, &claims) { - t.Errorf("Expected %+v, but got %+v", claims, got) - } -} - -func TestClient_VerifyToken_ExpiredToken(t *testing.T) { - c, _ := NewClient("token") - - expiredClaims := dummySessionClaims - expiredClaims.Expiry = jwt.NewNumericDate(time.Now().Add(time.Second * -1)) - token, pubKey := testGenerateTokenJWT(t, expiredClaims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - - _, err := c.VerifyToken(token) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestClient_VerifyToken_InvalidAuthorizedParty(t *testing.T) { - c, _ := NewClient("token") - - claims := dummySessionClaims - claims.AuthorizedParty = "fake-party" - - token, pubKey := testGenerateTokenJWT(t, claims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - - _, err := c.VerifyToken(token, WithAuthorizedParty("authorized_party")) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestClient_VerifyToken_Success(t *testing.T) { - c, _ := NewClient("token") - token, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - - got, _ := c.VerifyToken(token) - if !reflect.DeepEqual(got, &dummySessionClaims) { - t.Errorf("Expected %+v, but got %+v", dummySessionClaims, got) - } -} - -func TestClient_VerifyToken_Success_NewIssuerFormat(t *testing.T) { - c, _ := NewClient("token") - - claims := dummySessionClaims - claims.Issuer = "https://foo-bar-13.clerk.accounts.dev" - - token, pubKey := testGenerateTokenJWT(t, claims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - - got, err := c.VerifyToken(token) - if err != nil { - t.Fatalf("Expected no error but got %v", err) - } - - if !reflect.DeepEqual(got, &claims) { - t.Errorf("Expected %+v, but got %+v", claims, got) - } -} - -func TestClient_VerifyToken_Success_ExpiredCache(t *testing.T) { - c, mux, _, teardown := setup("token") - defer teardown() - - token, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid") - - mux.HandleFunc("/jwks", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - _ = json.NewEncoder(w).Encode(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - }) - - client := c.(*client) - client.jwksCache.expiresAt = time.Now().Add(time.Second * -5) - - got, _ := c.VerifyToken(token) - if !reflect.DeepEqual(got, &dummySessionClaims) { - t.Errorf("Expected %+v, but got %+v", dummySessionClaims, got) - } -} - -func TestClient_VerifyToken_Success_AuthorizedParty(t *testing.T) { - c, mux, _, teardown := setup("token") - defer teardown() - - token, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid") - - mux.HandleFunc("/jwks", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - _ = json.NewEncoder(w).Encode(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - }) - - client := c.(*client) - client.jwksCache.expiresAt = time.Now().Add(time.Second * -5) - - got, _ := c.VerifyToken(token, WithAuthorizedParty("authorized_party")) - if !reflect.DeepEqual(got, &dummySessionClaims) { - t.Errorf("Expected %+v, but got %+v", dummySessionClaims, got) - } -} - -func TestClient_VerifyToken_Success_WithLeeway(t *testing.T) { - c, _ := NewClient("token") - - expiredClaims := dummySessionClaims - expiredClaims.Expiry = jwt.NewNumericDate(time.Now().Add(time.Second * -1)) - token, pubKey := testGenerateTokenJWT(t, expiredClaims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - - _, err := c.VerifyToken(token, WithLeeway(3*time.Second)) - if err != nil { - t.Errorf("Expected no error to be returned, but got: %+v", err) - } -} - -func TestClient_VerifyToken_Success_WithJWTVerificationKey(t *testing.T) { - c, _ := NewClient("token") - token, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid") - verificationKey := testRSAPublicKeyToPEM(t, pubKey) - - _, err := c.VerifyToken(token, WithJWTVerificationKey(verificationKey)) - if err != nil { - t.Errorf("Expected no error, but got: %+v", err) - } -} - -func TestClient_VerifyToken_Success_WithCustomClaims(t *testing.T) { - c, _ := NewClient("token") - - expectedClaims := map[string]interface{}{ - "iss": "https://clerk.issuer", - "sub": "subject", - "sid": "session_id", - "azp": "authorized_party", - "role": "tester", - "interests": []string{"tennis", "football"}, - } - - token, pubKey := testGenerateTokenJWT(t, expectedClaims, "kid") - - client := c.(*client) - client.jwksCache.set(testBuildJWKS(t, pubKey, jose.RS256, "kid")) - - customClaims := struct { - Issuer string `json:"iss"` - UserID string `json:"sub"` - Role string `json:"role"` - Interests []string `json:"interests"` - }{} - - _, _ = c.VerifyToken(token, WithCustomClaims(&customClaims)) - assert.Equal(t, expectedClaims["iss"], customClaims.Issuer) - assert.Equal(t, expectedClaims["sub"], customClaims.UserID) - assert.Equal(t, expectedClaims["role"], customClaims.Role) - assert.Equal(t, expectedClaims["interests"], customClaims.Interests) -} - -func TestClient_VerifyToken_Error_WithJWTVerificationKey(t *testing.T) { - c, _ := NewClient("token") - token, _ := testGenerateTokenJWT(t, dummySessionClaims, "kid") - - // Generate new public key not matching the one from the token - _, pubKey := testGenerateTokenJWT(t, dummySessionClaims, "kid") - verificationKey := testRSAPublicKeyToPEM(t, pubKey) - - _, err := c.VerifyToken(token, WithJWTVerificationKey(verificationKey)) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func testGenerateTokenJWT(t *testing.T, claims interface{}, kid string) (string, crypto.PublicKey) { - t.Helper() - - privKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - t.Error(err) - } - - signerOpts := &jose.SignerOptions{} - signerOpts.WithType("JWT") - if kid != "" { - signerOpts.WithHeader("kid", kid) - } - - signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: privKey}, signerOpts) - if err != nil { - t.Error(err) - } - - builder := jwt.Signed(signer) - builder = builder.Claims(claims) - - token, err := builder.CompactSerialize() - if err != nil { - t.Error(err) - } - - return token, privKey.Public() -} - -func testBuildJWKS(t *testing.T, pubKey crypto.PublicKey, alg jose.SignatureAlgorithm, kid string) *JWKS { - t.Helper() - - return &JWKS{Keys: []jose.JSONWebKey{ - { - Key: pubKey, - KeyID: kid, - Algorithm: string(alg), - Use: "sig", - }, - }} -} - -func testRSAPublicKeyToPEM(t *testing.T, input interface{}) string { - t.Helper() - - rsaPubKey, ok := input.(*rsa.PublicKey) - if !ok { - t.Error("provided input is not an RSA public key") - } - - pubKeyBytes, err := x509.MarshalPKIXPublicKey(rsaPubKey) - if err != nil { - t.Error("failed to marshal public key") - } - - pemBytes := pem.EncodeToMemory(&pem.Block{ - Type: "PUBLIC KEY", - Bytes: pubKeyBytes, - }) - - return string(pemBytes) -} diff --git a/clerk/users.go b/clerk/users.go deleted file mode 100644 index 635da3e0..00000000 --- a/clerk/users.go +++ /dev/null @@ -1,360 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "strconv" -) - -type UsersService service - -type User struct { - ID string `json:"id"` - Object string `json:"object"` - Username *string `json:"username"` - FirstName *string `json:"first_name"` - LastName *string `json:"last_name"` - Gender *string `json:"gender"` - Birthday *string `json:"birthday"` - ProfileImageURL string `json:"profile_image_url"` - ImageURL *string `json:"image_url,omitempty"` - HasImage bool `json:"has_image"` - PrimaryEmailAddressID *string `json:"primary_email_address_id"` - PrimaryPhoneNumberID *string `json:"primary_phone_number_id"` - PrimaryWeb3WalletID *string `json:"primary_web3_wallet_id"` - PasswordEnabled bool `json:"password_enabled"` - TwoFactorEnabled bool `json:"two_factor_enabled"` - TOTPEnabled bool `json:"totp_enabled"` - BackupCodeEnabled bool `json:"backup_code_enabled"` - EmailAddresses []EmailAddress `json:"email_addresses"` - PhoneNumbers []PhoneNumber `json:"phone_numbers"` - Web3Wallets []Web3Wallet `json:"web3_wallets"` - ExternalAccounts []interface{} `json:"external_accounts"` - PublicMetadata interface{} `json:"public_metadata"` - PrivateMetadata interface{} `json:"private_metadata"` - UnsafeMetadata interface{} `json:"unsafe_metadata"` - LastSignInAt *int64 `json:"last_sign_in_at"` - Banned bool `json:"banned"` - Locked bool `json:"locked"` - LockoutExpiresInSeconds *int64 `json:"lockout_expires_in_seconds"` - VerificationAttemptsRemaining *int64 `json:"verification_attempts_remaining"` - ExternalID *string `json:"external_id"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` - LastActiveAt *int64 `json:"last_active_at"` -} - -type UserOAuthAccessToken struct { - Object string `json:"object"` - Token string `json:"token"` - Provider string `json:"provider"` - PublicMetadata json.RawMessage `json:"public_metadata"` - Label *string `json:"label"` - Scopes []string `json:"scopes"` - TokenSecret *string `json:"token_secret"` -} - -type IdentificationLink struct { - IdentType string `json:"type"` - IdentID string `json:"id"` -} - -type CreateUserParams struct { - EmailAddresses []string `json:"email_address,omitempty"` - PhoneNumbers []string `json:"phone_number,omitempty"` - Web3Wallets []string `json:"web3_wallet,omitempty"` - Username *string `json:"username,omitempty"` - Password *string `json:"password,omitempty"` - FirstName *string `json:"first_name,omitempty"` - LastName *string `json:"last_name,omitempty"` - ExternalID *string `json:"external_id,omitempty"` - UnsafeMetadata *json.RawMessage `json:"unsafe_metadata,omitempty"` - PublicMetadata *json.RawMessage `json:"public_metadata,omitempty"` - PrivateMetadata *json.RawMessage `json:"private_metadata,omitempty"` - PasswordDigest *string `json:"password_digest,omitempty"` - PasswordHasher *string `json:"password_hasher,omitempty"` - SkipPasswordRequirement *bool `json:"skip_password_requirement,omitempty"` - SkipPasswordChecks *bool `json:"skip_password_checks,omitempty"` - TOTPSecret *string `json:"totp_secret,omitempty"` - BackupCodes []string `json:"backup_codes,omitempty"` - // Specified in RFC3339 format - CreatedAt *string `json:"created_at,omitempty"` -} - -func (s *UsersService) Create(params CreateUserParams) (*User, error) { - req, _ := s.client.NewRequest("POST", UsersUrl, ¶ms) - var user User - _, err := s.client.Do(req, &user) - if err != nil { - return nil, err - } - return &user, nil -} - -type ListAllUsersParams struct { - Limit *int - Offset *int - EmailAddresses []string - ExternalIDs []string - PhoneNumbers []string - Web3Wallets []string - Usernames []string - UserIDs []string - Query *string - LastActiveAtSince *int64 - OrderBy *string -} - -func (s *UsersService) ListAll(params ListAllUsersParams) ([]User, error) { - req, _ := s.client.NewRequest("GET", UsersUrl) - - s.addUserSearchParamsToRequest(req, params) - - paginationParams := PaginationParams{Limit: params.Limit, Offset: params.Offset} - query := req.URL.Query() - addPaginationParams(query, paginationParams) - if params.OrderBy != nil { - query.Add("order_by", *params.OrderBy) - } - req.URL.RawQuery = query.Encode() - - var users []User - _, err := s.client.Do(req, &users) - if err != nil { - return nil, err - } - return users, nil -} - -type UserCount struct { - Object string `json:"object"` - TotalCount int `json:"total_count"` -} - -func (s *UsersService) Count(params ListAllUsersParams) (*UserCount, error) { - req, _ := s.client.NewRequest("GET", UsersCountUrl) - - s.addUserSearchParamsToRequest(req, params) - - var userCount UserCount - _, err := s.client.Do(req, &userCount) - if err != nil { - return nil, err - } - return &userCount, nil -} - -func (s *UsersService) addUserSearchParamsToRequest(r *http.Request, params ListAllUsersParams) { - query := r.URL.Query() - if params.EmailAddresses != nil { - for _, email := range params.EmailAddresses { - query.Add("email_address", email) - } - } - if params.PhoneNumbers != nil { - for _, phone := range params.PhoneNumbers { - query.Add("phone_number", phone) - } - } - if params.ExternalIDs != nil { - for _, externalID := range params.ExternalIDs { - query.Add("external_id", externalID) - } - } - if params.Web3Wallets != nil { - for _, web3Wallet := range params.Web3Wallets { - query.Add("web3_wallet", web3Wallet) - } - } - if params.Usernames != nil { - for _, username := range params.Usernames { - query.Add("username", username) - } - } - if params.UserIDs != nil { - for _, userID := range params.UserIDs { - query.Add("user_id", userID) - } - } - if params.Query != nil { - query.Add("query", *params.Query) - } - if params.LastActiveAtSince != nil { - query.Add("last_active_at_since", strconv.Itoa(int(*params.LastActiveAtSince))) - } - r.URL.RawQuery = query.Encode() -} - -func (s *UsersService) Read(userId string) (*User, error) { - userUrl := fmt.Sprintf("%s/%s", UsersUrl, userId) - req, _ := s.client.NewRequest("GET", userUrl) - - var user User - _, err := s.client.Do(req, &user) - if err != nil { - return nil, err - } - return &user, nil -} - -func (s *UsersService) Delete(userId string) (*DeleteResponse, error) { - userUrl := fmt.Sprintf("%s/%s", UsersUrl, userId) - req, _ := s.client.NewRequest("DELETE", userUrl) - - var delResponse DeleteResponse - if _, err := s.client.Do(req, &delResponse); err != nil { - return nil, err - } - return &delResponse, nil -} - -type UpdateUser struct { - FirstName *string `json:"first_name,omitempty"` - LastName *string `json:"last_name,omitempty"` - PrimaryEmailAddressID *string `json:"primary_email_address_id,omitempty"` - PrimaryPhoneNumberID *string `json:"primary_phone_number_id,omitempty"` - PrimaryWeb3WalletID *string `json:"primary_web3_wallet_id,omitempty"` - Username *string `json:"username,omitempty"` - ProfileImageID *string `json:"profile_image_id,omitempty"` - ProfileImage *string `json:"profile_image,omitempty"` - Password *string `json:"password,omitempty"` - SkipPasswordChecks *bool `json:"skip_password_checks,omitempty"` - SignOutOfOtherSessions *bool `json:"sign_out_of_other_sessions,omitempty"` - ExternalID *string `json:"external_id,omitempty"` - PublicMetadata interface{} `json:"public_metadata,omitempty"` - PrivateMetadata interface{} `json:"private_metadata,omitempty"` - UnsafeMetadata interface{} `json:"unsafe_metadata,omitempty"` - TOTPSecret *string `json:"totp_secret,omitempty"` - BackupCodes []string `json:"backup_codes,omitempty"` - // Specified in RFC3339 format - CreatedAt *string `json:"created_at,omitempty"` -} - -func (s *UsersService) Update(userId string, updateRequest *UpdateUser) (*User, error) { - userUrl := fmt.Sprintf("%s/%s", UsersUrl, userId) - req, _ := s.client.NewRequest("PATCH", userUrl, updateRequest) - - var updatedUser User - _, err := s.client.Do(req, &updatedUser) - if err != nil { - return nil, err - } - return &updatedUser, nil -} - -type UpdateUserMetadata struct { - PublicMetadata json.RawMessage `json:"public_metadata"` - PrivateMetadata json.RawMessage `json:"private_metadata"` - UnsafeMetadata json.RawMessage `json:"unsafe_metadata"` -} - -func (s *UsersService) UpdateMetadata(userId string, updateMetadataRequest *UpdateUserMetadata) (*User, error) { - updateUserMetadataURL := fmt.Sprintf("%s/%s/metadata", UsersUrl, userId) - req, _ := s.client.NewRequest(http.MethodPatch, updateUserMetadataURL, updateMetadataRequest) - - var updatedUser User - _, err := s.client.Do(req, &updatedUser) - if err != nil { - return nil, err - } - return &updatedUser, nil -} - -func (s *UsersService) ListOAuthAccessTokens(userID, provider string) ([]*UserOAuthAccessToken, error) { - url := fmt.Sprintf("%s/%s/oauth_access_tokens/%s", UsersUrl, userID, provider) - req, _ := s.client.NewRequest(http.MethodGet, url) - - response := make([]*UserOAuthAccessToken, 0) - _, err := s.client.Do(req, &response) - if err != nil { - return nil, err - } - return response, nil -} - -type UserDisableMFAResponse struct { - UserID string `json:"user_id"` -} - -func (s *UsersService) DisableMFA(userID string) (*UserDisableMFAResponse, error) { - url := fmt.Sprintf("%s/%s/mfa", UsersUrl, userID) - req, _ := s.client.NewRequest(http.MethodDelete, url) - - var response UserDisableMFAResponse - if _, err := s.client.Do(req, &response); err != nil { - return nil, err - } - - return &response, nil -} - -func (s *UsersService) Ban(userID string) (*User, error) { - url := fmt.Sprintf("%s/%s/ban", UsersUrl, userID) - req, _ := s.client.NewRequest(http.MethodPost, url) - - var response User - if _, err := s.client.Do(req, &response); err != nil { - return nil, err - } - - return &response, nil -} - -func (s *UsersService) Unban(userID string) (*User, error) { - url := fmt.Sprintf("%s/%s/unban", UsersUrl, userID) - req, _ := s.client.NewRequest(http.MethodPost, url) - - var response User - if _, err := s.client.Do(req, &response); err != nil { - return nil, err - } - - return &response, nil -} - -func (s *UsersService) Lock(userID string) (*User, error) { - url := fmt.Sprintf("%s/%s/lock", UsersUrl, userID) - req, _ := s.client.NewRequest(http.MethodPost, url) - - var response User - if _, err := s.client.Do(req, &response); err != nil { - return nil, err - } - - return &response, nil -} - -func (s *UsersService) Unlock(userID string) (*User, error) { - url := fmt.Sprintf("%s/%s/unlock", UsersUrl, userID) - req, _ := s.client.NewRequest(http.MethodPost, url) - - var response User - if _, err := s.client.Do(req, &response); err != nil { - return nil, err - } - - return &response, nil -} - -type ListMembershipsParams struct { - Limit *int - Offset *int - UserID string -} - -func (s *UsersService) ListMemberships(params ListMembershipsParams) (*ListOrganizationMembershipsResponse, error) { - req, _ := s.client.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s/organization_memberships", UsersUrl, params.UserID)) - - paginationParams := PaginationParams{Limit: params.Limit, Offset: params.Offset} - query := req.URL.Query() - addPaginationParams(query, paginationParams) - req.URL.RawQuery = query.Encode() - - var response ListOrganizationMembershipsResponse - if _, err := s.client.Do(req, &response); err != nil { - return nil, err - } - - return &response, nil -} diff --git a/clerk/users_test.go b/clerk/users_test.go deleted file mode 100644 index f24f1826..00000000 --- a/clerk/users_test.go +++ /dev/null @@ -1,597 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - "reflect" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestUsersService_Create_happyPath(t *testing.T) { - token := "token" - var payload CreateUserParams - _ = json.Unmarshal([]byte(dummyCreateUserRequestJson), &payload) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/users", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPost) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyUserJson) - }) - - got, err := client.Users().Create(payload) - - var want User - _ = json.Unmarshal([]byte(dummyUserJson), &want) - - assert.Nil(t, err) - assert.Equal(t, want, *got) -} - -func TestUsersService_Create_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.Users().Create(CreateUserParams{}) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestUsersService_ListAll_happyPath(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := "[" + dummyUserJson + "]" - - mux.HandleFunc("/users", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - fmt.Fprint(w, expectedResponse) - }) - - var want []User - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Users().ListAll(ListAllUsersParams{}) - if len(got) != len(want) { - t.Errorf("Was expecting %d user to be returned, instead got %d", len(want), len(got)) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestUsersService_ListAll_happyPathWithParameters(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := "[" + dummyUserJson + "]" - - mux.HandleFunc("/users", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - - actualQuery := req.URL.Query() - expectedQuery := url.Values(map[string][]string{ - "limit": {"5"}, - "offset": {"6"}, - "email_address": {"email1", "email2"}, - "phone_number": {"phone1", "phone2"}, - "web3_wallet": {"wallet1", "wallet2"}, - "username": {"username1", "username2"}, - "user_id": {"userid1", "userid2"}, - "query": {"my-query"}, - "last_active_at_since": {"1700690400000"}, - "order_by": {"created_at"}, - }) - assert.Equal(t, expectedQuery, actualQuery) - fmt.Fprint(w, expectedResponse) - }) - - var want []User - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Users().ListAll(ListAllUsersParams{ - Limit: intToPtr(5), - Offset: intToPtr(6), - EmailAddresses: []string{"email1", "email2"}, - PhoneNumbers: []string{"phone1", "phone2"}, - Web3Wallets: []string{"wallet1", "wallet2"}, - Usernames: []string{"username1", "username2"}, - UserIDs: []string{"userid1", "userid2"}, - Query: stringToPtr("my-query"), - LastActiveAtSince: int64ToPtr(int64(1700690400000)), - OrderBy: stringToPtr("created_at"), - }) - if len(got) != len(want) { - t.Errorf("Was expecting %d user to be returned, instead got %d", len(want), len(got)) - } - - if !reflect.DeepEqual(got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestUsersService_ListAll_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - users, err := client.Users().ListAll(ListAllUsersParams{}) - if err == nil { - t.Errorf("Expected error to be returned") - } - if users != nil { - t.Errorf("Was not expecting any users to be returned, instead got %v", users) - } -} - -func TestUsersService_Count_happyPath(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := dummyUserCountJson - - mux.HandleFunc("/users/count", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - fmt.Fprint(w, expectedResponse) - }) - - var want UserCount - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Users().Count(ListAllUsersParams{}) - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -func TestUsersService_Count_happyPathWithParameters(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := dummyUserCountJson - - mux.HandleFunc("/users/count", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer token") - - actualQuery := req.URL.Query() - expectedQuery := url.Values(map[string][]string{ - "email_address": {"email1", "email2"}, - "phone_number": {"phone1", "phone2"}, - "web3_wallet": {"wallet1", "wallet2"}, - "username": {"username1", "username2"}, - "user_id": {"userid1", "userid2"}, - "query": {"my-query"}, - }) - assert.Equal(t, expectedQuery, actualQuery) - fmt.Fprint(w, expectedResponse) - }) - - var want UserCount - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Users().Count(ListAllUsersParams{ - EmailAddresses: []string{"email1", "email2"}, - PhoneNumbers: []string{"phone1", "phone2"}, - Web3Wallets: []string{"wallet1", "wallet2"}, - Usernames: []string{"username1", "username2"}, - UserIDs: []string{"userid1", "userid2"}, - Query: stringToPtr("my-query"), - }) - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestUsersService_Count_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - users, err := client.Users().Count(ListAllUsersParams{}) - if err == nil { - t.Errorf("Expected error to be returned") - } - if users != nil { - t.Errorf("Was not expecting any users to be returned, instead got %v", users) - } -} - -func TestUsersService_Read_happyPath(t *testing.T) { - token := "token" - userId := "someUserId" - expectedResponse := dummyUserJson - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/users/"+userId, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, expectedResponse) - }) - - var want User - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Users().Read(userId) - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -func TestUsersService_Read_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - user, err := client.Users().Read("someUserId") - if err == nil { - t.Errorf("Expected error to be returned") - } - if user != nil { - t.Errorf("Was not expecting any user to be returned, instead got %v", user) - } -} - -func TestUsersService_Delete_happyPath(t *testing.T) { - token := "token" - userId := "someUserId" - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/users/"+userId, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "DELETE") - testHeader(t, req, "Authorization", "Bearer "+token) - response := fmt.Sprintf(`{ "deleted": true, "id": "%v", "object": "user" }`, userId) - fmt.Fprint(w, response) - }) - - want := DeleteResponse{ID: userId, Object: "user", Deleted: true} - - got, _ := client.Users().Delete(userId) - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -func TestUsersService_Delete_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - delResponse, err := client.Users().Delete("someUserId") - if err == nil { - t.Errorf("Expected error to be returned") - } - if delResponse != nil { - t.Errorf("Was not expecting any reponse to be returned, instead got %v", delResponse) - } -} - -func TestUsersService_Update_happyPath(t *testing.T) { - token := "token" - userId := "someUserId" - var payload UpdateUser - _ = json.Unmarshal([]byte(dummyUpdateRequestJson), &payload) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/users/"+userId, func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "PATCH") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyUserJson) - }) - - got, _ := client.Users().Update(userId, &payload) - - var want User - _ = json.Unmarshal([]byte(dummyUserJson), &want) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, payload) - } -} - -func TestUsersService_Update_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.Users().Update("someUserId", nil) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestUsersService_UpdateMetadata_happyPath(t *testing.T) { - token := "token" - userId := "someUserId" - var payload UpdateUserMetadata - _ = json.Unmarshal([]byte(dummyUpdateMetadataRequestJson), &payload) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/users/"+userId+"/metadata", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPatch) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyUserJson) - }) - - got, _ := client.Users().UpdateMetadata(userId, &payload) - - var want User - _ = json.Unmarshal([]byte(dummyUserJson), &want) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, payload) - } -} - -func TestUsersService_UpdateMetadata_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.Users().UpdateMetadata("someUserId", nil) - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestUsersService_ListOAuthAccessTokens_happyPath(t *testing.T) { - token := "token" - userId := "someUserId" - provider := "testProvider" - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc(fmt.Sprintf("/users/%s/oauth_access_tokens/%s", userId, provider), func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "GET") - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyUserOAuthAccessTokensJson) - }) - - got, _ := client.Users().ListOAuthAccessTokens(userId, provider) - - want := make([]*UserOAuthAccessToken, 0) - _ = json.Unmarshal([]byte(dummyUserOAuthAccessTokensJson), &want) - - if !reflect.DeepEqual(got, want) { - t.Errorf("Response = %v, want %v", got, want) - } -} - -func TestUsersService_ListOAuthAccessTokens_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - _, err := client.Users().ListOAuthAccessTokens("someUserId", "testProvider") - if err == nil { - t.Errorf("Expected error to be returned") - } -} - -func TestUsersService_DisableMFA_happyPath(t *testing.T) { - token := "token" - userID := "test-user-id" - var payload UpdateUserMetadata - _ = json.Unmarshal([]byte(dummyUpdateMetadataRequestJson), &payload) - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/users/"+userID+"/mfa", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodDelete) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, `{"user_id":"`+userID+`"}`) - }) - - got, err := client.Users().DisableMFA(userID) - assert.NoError(t, err) - assert.Equal(t, userID, got.UserID) -} - -func TestUsersService_Ban_happyPath(t *testing.T) { - token := "token" - userID := "test-user-id" - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/users/"+userID+"/ban", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPost) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyUserJson) - }) - - _, err := client.Users().Ban(userID) - assert.NoError(t, err) -} - -func TestUsersService_Unban_happyPath(t *testing.T) { - token := "token" - userID := "test-user-id" - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/users/"+userID+"/unban", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPost) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyUserJson) - }) - - _, err := client.Users().Unban(userID) - assert.NoError(t, err) -} - -func TestUsersService_Lock_happyPath(t *testing.T) { - token := "token" - userID := "test-user-id" - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/users/"+userID+"/lock", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPost) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyUserJson) - }) - - _, err := client.Users().Lock(userID) - assert.NoError(t, err) -} - -func TestUsersService_Unlock_happyPath(t *testing.T) { - token := "token" - userID := "test-user-id" - - client, mux, _, teardown := setup(token) - defer teardown() - - mux.HandleFunc("/users/"+userID+"/unlock", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, http.MethodPost) - testHeader(t, req, "Authorization", "Bearer "+token) - fmt.Fprint(w, dummyUserJson) - }) - - _, err := client.Users().Unlock(userID) - assert.NoError(t, err) -} - -const dummyUserJson = `{ - "birthday": "", - "created_at": 1610783813, - "email_addresses": [ - { - "email_address": "iron_man@avengers.com", - "id": "idn_1mebQ9KkZWrhbQGF8Yj", - "linked_to": [ - { - "id": "idn_1n8tzrjmoKzHtQkaFe1pvK1OqLr", - "type": "oauth_google" - } - ], - "object": "email_address", - "verification": { - "status": "verified", - "strategy": "from_oauth_google" - } - } - ], - "external_accounts": [ - { - "approved_scopes": "email https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile openid profile", - "email_address": "iron_man@avengers.com", - "family_name": "Stark", - "given_name": "Tony", - "google_id": "11031040442607", - "id": "idn_1mebQ8sPZOtb7UQgptk", - "object": "google_account", - "picture": "https://lh3.googleusercontent.com/a-/AOh14Gg-UlYe7Pzd8vngVKdFlNCuGTn7cqxx=s96-c" - } - ], - "first_name": "Anthony", - "gender": "", - "id": "user_1mebQggrD3xO5JfuHk7clQ94ysA", - "last_name": "Stark", - "object": "user", - "password_enabled": false, - "phone_numbers": [], - "primary_email_address_id": "idn_1n8tzqi8K5ydvb1K7RJEKjT7Wb8", - "primary_phone_number_id": null, - "profile_image_url": "https://lh3.googleusercontent.com/a-/AOh14Gg-UlYe7PzddYKJRu2r8vGTn7cqxx=s96-c", - "two_factor_enabled": false, - "updated_at": 1610783813, - "username": null, - "public_metadata": { - "address": { - "street": "Pennsylvania Avenue", - "number": "1600" - } - }, - "private_metadata": { - "app_id": 5 - }, - "last_sign_in_at": 1610783813000, - "banned": false, - "locked": false, - "lockout_expires_in_seconds": null, - "verification_attempts_remaining": null, - "last_active_at": 1700690400000 - }` - -const dummyUserOAuthAccessTokensJson = `[ - { - "object": "oauth_access_token", - "token": "test_token", - "provider": "oauth_testProvider", - "public_metadata": {}, - "label": null, - "scopes": [ - "user:read", - "user:write" - ] - } - ]` - -const dummyCreateUserRequestJson = `{ - "first_name": "Tony", - "last_name": "Stark", - "email_address": ["email@example.com"], - "phone_number": ["+30123456789"], - "password": "new_password", - "public_metadata": { - "address": { - "street": "Pennsylvania Avenue", - "number": "1600" - } - }, - "private_metadata": { - app_id: 5 - }, - "unsafe_metadata": { - viewed_profile: true - }, - "totp_secret": "AICJ3HCXKO4KOY6NDH6RII4E3ZYL5ZBH", - }` - -const dummyUpdateRequestJson = `{ - "first_name": "Tony", - "last_name": "Stark", - "primary_email_address_id": "some_image_id", - "primary_phone_number_id": "some_phone_id", - "profile_image": "some_profile_image", - "password": "new_password", - "public_metadata": { - "address": { - "street": "Pennsylvania Avenue", - "number": "1600" - } - }, - "private_metadata": { - app_id: 5 - }, - "unsafe_metadata": { - viewed_profile: true - }, - }` - -const dummyUpdateMetadataRequestJson = `{ - "public_metadata": { - "value": "public_value", - }, - "private_metadata": { - "contact_id": "some_contact_id", - }, - "unsafe_metadata": { - viewed_profile: true - }, - }` - -const dummyUserCountJson = `{ - "object": "total_count", - "total_count": 2 - }` diff --git a/clerk/verification.go b/clerk/verification.go deleted file mode 100644 index 2ec6a136..00000000 --- a/clerk/verification.go +++ /dev/null @@ -1,78 +0,0 @@ -package clerk - -import ( - "errors" - "net/http" -) - -const ( - CookieSession = "__session" - QueryParamSessionId = "_clerk_session_id" -) - -type VerificationService service - -type verifyRequest struct { - Token string `json:"token"` -} - -type Verification struct { - Status string `json:"status"` - Strategy string `json:"strategy"` - Attempts *int `json:"attempts"` - ExpireAt *int64 `json:"expire_at"` - VerifiedAtClient string `json:"verified_at_client,omitempty"` - - // needed for Web3 - Nonce *string `json:"nonce,omitempty"` - - // needed for OAuth - ExternalVerificationRedirectURL *string `json:"external_verification_redirect_url,omitempty"` - Error []byte `json:"error,omitempty"` -} - -func (s *VerificationService) Verify(req *http.Request) (*Session, error) { - if req == nil { - return nil, errors.New("cannot verify empty request") - } - cookie, err := req.Cookie(CookieSession) - if err != nil { - return nil, errors.New("couldn't find cookie " + CookieSession) - } - - sessionToken := cookie.Value - sessionId := req.URL.Query().Get(QueryParamSessionId) - - if sessionId == "" { - return s.useClientActiveSession(sessionToken) - } - - return s.client.Sessions().Verify(sessionId, sessionToken) -} - -func (s *VerificationService) useClientActiveSession(token string) (*Session, error) { - clientResponse, err := s.client.Clients().Verify(token) - if err != nil { - return nil, err - } - - if clientResponse.LastActiveSessionID == nil { - return nil, errors.New("no active sessions for given client") - } - - for _, session := range clientResponse.Sessions { - if session.ID == *clientResponse.LastActiveSessionID { - return session, nil - } - } - - return nil, errors.New("active session not included in client's sessions") -} - -func doVerify(client Client, url, token string, response interface{}) error { - tokenPayload := verifyRequest{Token: token} - req, _ := client.NewRequest("POST", url, &tokenPayload) - - _, err := client.Do(req, response) - return err -} diff --git a/clerk/verification_test.go b/clerk/verification_test.go deleted file mode 100644 index 1015bd84..00000000 --- a/clerk/verification_test.go +++ /dev/null @@ -1,224 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - "reflect" - "testing" -) - -func TestVerificationService_Verify_useSessionId(t *testing.T) { - apiToken := "apiToken" - sessionId := "someSessionId" - sessionToken := "someSessionToken" - request := setupRequest(&sessionId, &sessionToken) - - client, mux, _, teardown := setup(apiToken) - defer teardown() - - expectedResponse := dummySessionJson - - mux.HandleFunc("/sessions/"+sessionId+"/verify", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - testHeader(t, req, "Authorization", "Bearer "+apiToken) - fmt.Fprint(w, expectedResponse) - }) - - got, err := client.Verification().Verify(request) - if err != nil { - t.Errorf("Was not expecting error to be returned, got %v instead", err) - } - - var want Session - _ = json.Unmarshal([]byte(expectedResponse), &want) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -func TestVerificationService_Verify_handleServerErrorWhenUsingSessionId(t *testing.T) { - sessionId := "someSessionId" - sessionToken := "someSessionToken" - request := setupRequest(&sessionId, &sessionToken) - - client, mux, _, teardown := setup("apiToken") - defer teardown() - - mux.HandleFunc("/sessions/"+sessionId+"/verify", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - w.WriteHeader(400) - }) - - _, err := client.Verification().Verify(request) - if err == nil { - t.Errorf("Was expecting error to be returned") - } -} - -func TestVerificationService_Verify_useClientActiveSession(t *testing.T) { - apiToken := "apiToken" - sessionToken := "someSessionToken" - request := setupRequest(nil, &sessionToken) - - client, mux, _, teardown := setup(apiToken) - defer teardown() - - clientResponseJson := dummyClientResponseJson - var clientResponse ClientResponse - _ = json.Unmarshal([]byte(clientResponseJson), &clientResponse) - - sessionJson := dummySessionJson - - mux.HandleFunc("/clients/verify", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - testHeader(t, req, "Authorization", "Bearer "+apiToken) - fmt.Fprint(w, clientResponseJson) - }) - - got, err := client.Verification().Verify(request) - if err != nil { - t.Errorf("Was not expecting error to be returned, got %v instead", err) - } - - var want Session - _ = json.Unmarshal([]byte(sessionJson), &want) - - if !reflect.DeepEqual(*got, want) { - t.Errorf("Response = %v, want %v", *got, want) - } -} - -func TestVerificationService_Verify_handleServerErrorWhenUsingClientActiveSession(t *testing.T) { - sessionToken := "someSessionToken" - request := setupRequest(nil, &sessionToken) - - client, mux, _, teardown := setup("apiToken") - defer teardown() - - mux.HandleFunc("/clients/verify", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - w.WriteHeader(400) - }) - - _, err := client.Verification().Verify(request) - if err == nil { - t.Errorf("Was expecting error to be returned") - } -} - -func TestVerificationService_Verify_noActiveSessionWhenUsingClientActiveSession(t *testing.T) { - apiToken := "apiToken" - sessionToken := "someSessionToken" - request := setupRequest(nil, &sessionToken) - - client, mux, _, teardown := setup(apiToken) - defer teardown() - - var clientResponse ClientResponse - _ = json.Unmarshal([]byte(dummyClientResponseJson), &clientResponse) - clientResponse.LastActiveSessionID = nil - - mux.HandleFunc("/clients/verify", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - jsonResp, _ := json.Marshal(clientResponse) - fmt.Fprint(w, string(jsonResp)) - }) - - _, err := client.Verification().Verify(request) - if err == nil { - t.Errorf("Was expecting error to be returned") - } -} - -func TestVerificationService_Verify_activeSessionNotIncludedInSessions(t *testing.T) { - apiToken := "apiToken" - sessionToken := "someSessionToken" - request := setupRequest(nil, &sessionToken) - - client, mux, _, teardown := setup(apiToken) - defer teardown() - - var clientResponse ClientResponse - _ = json.Unmarshal([]byte(dummyClientResponseJson), &clientResponse) - clientResponse.Sessions = make([]*Session, 0) - - mux.HandleFunc("/clients/verify", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - jsonResp, _ := json.Marshal(clientResponse) - fmt.Fprint(w, string(jsonResp)) - }) - - _, err := client.Verification().Verify(request) - if err == nil { - t.Errorf("Was expecting error to be returned") - } -} - -func TestVerificationService_Verify_notFailForEmptyRequest(t *testing.T) { - client, _, _, teardown := setup("apiToken") - defer teardown() - - session, err := client.Verification().Verify(nil) - if err == nil { - t.Errorf("Expected error to be returned") - } - - if session != nil { - t.Errorf("Expected no session to returned, got %v instead", session) - } -} - -func TestVerificationService_Verify_noSessionCookie(t *testing.T) { - client, _, _, teardown := setup("apiToken") - defer teardown() - - request := setupRequest(nil, nil) - - session, err := client.Verification().Verify(request) - if err == nil { - t.Errorf("Expected error to be returned") - } - - if session != nil { - t.Errorf("Expected no session to returned, got %v instead", session) - } -} - -func setupRequest(sessionId, sessionToken *string) *http.Request { - var request http.Request - request.Method = "GET" - - url := url.URL{ - Scheme: "http", - Host: "host.com", - Path: "/path", - } - request.URL = &url - - if sessionToken != nil { - // add session token as cookie - sessionCookie := http.Cookie{ - Name: CookieSession, - Value: *sessionToken, - } - request.Header = make(map[string][]string) - request.AddCookie(&sessionCookie) - } - - if sessionId != nil { - // add session id as query parameter - addQueryParam(&request, QueryParamSessionId, *sessionId) - } - - return &request -} - -func addQueryParam(req *http.Request, key, value string) { - url := req.URL - query := url.Query() - query.Add(key, value) - url.RawQuery = query.Encode() -} diff --git a/clerk/web3_wallets.go b/clerk/web3_wallets.go deleted file mode 100644 index 4ebfcf7b..00000000 --- a/clerk/web3_wallets.go +++ /dev/null @@ -1,8 +0,0 @@ -package clerk - -type Web3Wallet struct { - ID string `json:"id"` - Object string `json:"object"` - Web3Wallet string `json:"web3_wallet"` - Verification *Verification `json:"verification"` -} diff --git a/clerk/webhooks.go b/clerk/webhooks.go deleted file mode 100644 index a9fb08c1..00000000 --- a/clerk/webhooks.go +++ /dev/null @@ -1,38 +0,0 @@ -package clerk - -type WebhooksService service - -type SvixResponse struct { - SvixURL string `json:"svix_url"` -} - -func (s *WebhooksService) CreateSvix() (*SvixResponse, error) { - svixUrl := WebhooksUrl + "/svix" - req, _ := s.client.NewRequest("POST", svixUrl) - - var svixResponse SvixResponse - if _, err := s.client.Do(req, &svixResponse); err != nil { - return nil, err - } - return &svixResponse, nil -} - -func (s *WebhooksService) DeleteSvix() error { - svixUrl := WebhooksUrl + "/svix" - req, _ := s.client.NewRequest("DELETE", svixUrl) - - _, err := s.client.Do(req, nil) - - return err -} - -func (s *WebhooksService) RefreshSvixURL() (*SvixResponse, error) { - svixUrl := WebhooksUrl + "/svix_url" - req, _ := s.client.NewRequest("POST", svixUrl) - - var svixResponse SvixResponse - if _, err := s.client.Do(req, &svixResponse); err != nil { - return nil, err - } - return &svixResponse, nil -} diff --git a/clerk/webhooks_test.go b/clerk/webhooks_test.go deleted file mode 100644 index 453e0a49..00000000 --- a/clerk/webhooks_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package clerk - -import ( - "encoding/json" - "fmt" - "net/http" - "reflect" - "testing" -) - -func TestWebhooksService_CreateSvix_happyPath(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := dummySvixResponseJson - - mux.HandleFunc("/webhooks/svix", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - testHeader(t, req, "Authorization", "Bearer token") - fmt.Fprint(w, expectedResponse) - }) - - var want SvixResponse - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Webhooks().CreateSvix() - if !reflect.DeepEqual(*got, want) { - t.Errorf("response = %v, want %v", got, want) - } -} - -func TestWebhooksService_CreateSvix_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - svixResponse, err := client.Webhooks().CreateSvix() - if err == nil { - t.Errorf("expected error to be returned") - } - if svixResponse != nil { - t.Errorf("was not expecting any users to be returned, instead got %v", svixResponse) - } -} - -func TestWebhooksService_DeleteSvix_happyPath(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - mux.HandleFunc("/webhooks/svix", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "DELETE") - testHeader(t, req, "Authorization", "Bearer token") - w.WriteHeader(http.StatusNoContent) - }) - - err := client.Webhooks().DeleteSvix() - if err != nil { - t.Errorf("was not expecting error, found %v instead", err) - } -} - -func TestWebhooksService_DeleteSvix_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - err := client.Webhooks().DeleteSvix() - if err == nil { - t.Errorf("expected error to be returned") - } -} - -func TestWebhooksService_RefreshSvixURL_happyPath(t *testing.T) { - client, mux, _, teardown := setup("token") - defer teardown() - - expectedResponse := dummySvixResponseJson - - mux.HandleFunc("/webhooks/svix_url", func(w http.ResponseWriter, req *http.Request) { - testHttpMethod(t, req, "POST") - testHeader(t, req, "Authorization", "Bearer token") - fmt.Fprint(w, expectedResponse) - }) - - var want SvixResponse - _ = json.Unmarshal([]byte(expectedResponse), &want) - - got, _ := client.Webhooks().RefreshSvixURL() - if !reflect.DeepEqual(*got, want) { - t.Errorf("response = %v, want %v", got, want) - } -} - -func TestWebhooksService_RefreshSvixURL_invalidServer(t *testing.T) { - client, _ := NewClient("token") - - svixResponse, err := client.Webhooks().RefreshSvixURL() - if err == nil { - t.Errorf("expected error to be returned") - } - if svixResponse != nil { - t.Errorf("was not expecting any users to be returned, instead got %v", svixResponse) - } -} - -const dummySvixResponseJson = `{ - "svix_url": "http://example.svix.com" -}` diff --git a/clerk_test.go b/clerk_test.go new file mode 100644 index 00000000..75792f78 --- /dev/null +++ b/clerk_test.go @@ -0,0 +1,344 @@ +package clerk + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewAPIResponse(t *testing.T) { + body := []byte(`{"foo":"bar"}`) + resp := &http.Response{ + Status: "200 OK", + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(`{}`))), + Header: http.Header(map[string][]string{ + "Clerk-Trace-Id": {"trace-id"}, + "x-custom-header": {"custom-header"}, + }), + } + res := NewAPIResponse(resp, body) + assert.Equal(t, body, []byte(res.RawJSON)) + assert.Equal(t, "200 OK", res.Status) + assert.Equal(t, http.StatusOK, res.StatusCode) + assert.Equal(t, "trace-id", res.TraceID) + assert.Equal(t, resp.Header, res.Header) +} + +func TestNewBackend(t *testing.T) { + withDefaults, ok := NewBackend(&BackendConfig{}).(*defaultBackend) + require.True(t, ok) + require.NotNil(t, withDefaults.HTTPClient) + assert.Equal(t, defaultHTTPTimeout, withDefaults.HTTPClient.Timeout) + assert.Equal(t, APIURL, withDefaults.URL) + + u := "https://some.other.url" + httpClient := &http.Client{} + config := &BackendConfig{ + URL: &u, + HTTPClient: httpClient, + } + withOverrides, ok := NewBackend(config).(*defaultBackend) + require.True(t, ok) + assert.Equal(t, u, withOverrides.URL) + assert.Equal(t, httpClient, withOverrides.HTTPClient) +} + +func TestGetBackend_DataRace(t *testing.T) { + wg := &sync.WaitGroup{} + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + b, ok := GetBackend().(*defaultBackend) + require.True(t, ok) + assert.Equal(t, APIURL, b.URL) + }() + } + wg.Wait() +} + +func TestAPIErrorResponse(t *testing.T) { + // API error response that is valid JSON. The error + // string is the raw JSON. + resp := &APIErrorResponse{ + HTTPStatusCode: 200, + TraceID: "trace-id", + Errors: []Error{ + { + Code: "error-code", + Message: "message", + LongMessage: "long message", + }, + }, + } + expected := fmt.Sprintf(`{ + "status":%d, + "clerk_trace_id":"%s", + "errors":[{"code":"%s","message":"%s","long_message":"%s"}] +}`, + resp.HTTPStatusCode, + resp.TraceID, + resp.Errors[0].Code, + resp.Errors[0].Message, + resp.Errors[0].LongMessage, + ) + assert.JSONEq(t, expected, resp.Error()) +} + +// This is how you define a Clerk API resource which is ready to be +// used by the library. +type testResource struct { + APIResource + ID string `json:"id"` + Object string `json:"object"` +} + +// This is how you define types which can be used as Clerk API +// request parameters. +type testResourceParams struct { + APIParams + Name string `json:"name"` +} + +// This is how you define a Clerk API resource which can be used in +// API operations that read a list of resources. +type testResourceList struct { + APIResource + Resources []testResource `json:"data"` + TotalCount int64 `json:"total_count"` +} + +// This is how you define a type which can be used as parameters +// to a Clerk API operation that lists resources. +type testResourceListParams struct { + APIParams + ListParams + Name string `json:"name"` +} + +// We need to implement the Queryable interface. +func (params testResourceListParams) Add(q url.Values) { + q.Set("name", params.Name) + params.ListParams.Add(q) +} + +func TestBackendCall_RequestHeaders(t *testing.T) { + ctx := context.Background() + method := http.MethodPost + path := "/resources" + secretKey := "sk_test_123" + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, method, r.Method) + require.Equal(t, "/"+clerkAPIVersion+path, r.URL.Path) + + // The client sets the Authorization header correctly. + assert.Equal(t, fmt.Sprintf("Bearer %s", secretKey), r.Header.Get("Authorization")) + // The client sets the User-Agent header. + assert.Equal(t, "Clerk/v1 SDK-Go/v2.0.0", r.Header.Get("User-Agent")) + assert.Equal(t, "application/json", r.Header.Get("Content-Type")) + // The client includes a custom header with the SDK version. + assert.Equal(t, "go/v2.0.0", r.Header.Get("X-Clerk-SDK")) + + _, err := w.Write([]byte(`{}`)) + require.NoError(t, err) + })) + defer ts.Close() + + // Set up a mock backend which triggers requests to our test server above. + SetBackend(NewBackend(&BackendConfig{ + HTTPClient: ts.Client(), + URL: &ts.URL, + })) + + // Simulate usage for an API operation on a testResource. + // We need to initialize a request and use the Backend to send it. + SetKey(secretKey) + req := NewAPIRequest(method, path) + err := GetBackend().Call(ctx, req, &testResource{}) + require.NoError(t, err) +} + +// TestBackendCall_SuccessfulResponse_PostRequest tests that for POST +// requests (or other mutating operations) we serialize all parameters +// in the request body. +func TestBackendCall_SuccessfulResponse_PostRequest(t *testing.T) { + ctx := context.Background() + name := "the-name" + rawJSON := `{"id":"res_123","object":"resource"}` + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Assert that the request parameters were passed correctly in + // the request body. + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + defer r.Body.Close() + assert.JSONEq(t, fmt.Sprintf(`{"name":"%s"}`, name), string(body)) + + _, err = w.Write([]byte(rawJSON)) + require.NoError(t, err) + })) + defer ts.Close() + + // Set up a mock backend which triggers requests to our test server above. + SetBackend(NewBackend(&BackendConfig{ + HTTPClient: ts.Client(), + URL: &ts.URL, + })) + + // Simulate usage for an API operation on a testResource. + // We need to initialize a request and use the Backend to send it. + resource := &testResource{} + req := NewAPIRequest(http.MethodPost, "/resources") + req.SetParams(&testResourceParams{Name: name}) + err := GetBackend().Call(ctx, req, resource) + require.NoError(t, err) + + // The API response has been unmarshaled in the testResource struct. + assert.Equal(t, "resource", resource.Object) + assert.Equal(t, "res_123", resource.ID) + // We stored the API response + require.NotNil(t, resource.Response) + assert.JSONEq(t, rawJSON, string(resource.Response.RawJSON)) +} + +// TestBackendCall_SuccessfulResponse_GetRequest tests that for GET +// requests which don't have a body, we serialize any parameters in +// the URL query string. +func TestBackendCall_SuccessfulResponse_GetRequest(t *testing.T) { + ctx := context.Background() + name := "the-name" + limit := 1 + rawJSON := `{"data": [{"id":"res_123","object":"resource"}], "total_count": 1}` + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Assert that the request parameters were set in the URL + // query string. + q := r.URL.Query() + assert.Equal(t, name, q.Get("name")) + assert.Equal(t, strconv.Itoa(limit), q.Get("limit")) + // Optional query parameters are omitted. + _, ok := q["offset"] + assert.False(t, ok) + + _, err := w.Write([]byte(rawJSON)) + require.NoError(t, err) + })) + defer ts.Close() + + // Set up a mock backend which triggers requests to our test server above. + SetBackend(NewBackend(&BackendConfig{ + HTTPClient: ts.Client(), + URL: &ts.URL, + })) + + // Simulate usage for an API operation on a testResourceList. + // We need to initialize a request and use the Backend to send it. + resource := &testResourceList{} + req := NewAPIRequest(http.MethodGet, "/resources") + req.SetParams(&testResourceListParams{ + ListParams: ListParams{ + Limit: Int64(int64(limit)), + }, + Name: name, + }) + err := GetBackend().Call(ctx, req, resource) + require.NoError(t, err) + + // The API response has been unmarshaled correctly into a list of + // testResource structs. + assert.Equal(t, "resource", resource.Resources[0].Object) + assert.Equal(t, "res_123", resource.Resources[0].ID) + // We stored the API response + require.NotNil(t, resource.Response) + assert.JSONEq(t, rawJSON, string(resource.Response.RawJSON)) +} + +// TestBackendCall_ParseableError tests responses with a non-successful +// status code and a body that can be deserialized to an "expected" +// error response. These errors usually happen due to a client error +// and result in 4xx response statuses. The Clerk API responds with a +// familiar response body. +func TestBackendCall_ParseableError(t *testing.T) { + errorJSON := `{ + "clerk_trace_id": "trace-id", + "errors": [ + { + "code": "error-code", + "message": "error-message", + "long_message": "long-error-message", + "meta": { + "param_name": "param-name" + } + } + ] +}` + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, err := w.Write([]byte(errorJSON)) + require.NoError(t, err) + })) + defer ts.Close() + + SetBackend(NewBackend(&BackendConfig{ + HTTPClient: ts.Client(), + URL: &ts.URL, + })) + resource := &testResource{} + err := GetBackend().Call(context.Background(), NewAPIRequest(http.MethodPost, "/resources"), resource) + require.Error(t, err) + + // The error is an APIErrorResponse. We can assert on certain useful fields. + apiErr, ok := err.(*APIErrorResponse) + require.True(t, ok) + assert.Equal(t, http.StatusUnprocessableEntity, apiErr.HTTPStatusCode) + assert.Equal(t, "trace-id", apiErr.TraceID) + + // The response errors have been deserialized correctly. + require.Equal(t, 1, len(apiErr.Errors)) + assert.Equal(t, "error-code", apiErr.Errors[0].Code) + assert.Equal(t, "error-message", apiErr.Errors[0].Message) + assert.Equal(t, "long-error-message", apiErr.Errors[0].LongMessage) + assert.JSONEq(t, `{"param_name":"param-name"}`, string(apiErr.Errors[0].Meta)) + + // We've stored the raw response as well. + require.NotNil(t, apiErr.Response) + assert.JSONEq(t, errorJSON, string(apiErr.Response.RawJSON)) +} + +// TestBackendCall_ParseableError tests responses with a non-successful +// status code and a body that can be deserialized to an unexpected +// error response. This might happen when the Clerk API encounters an +// unexpected server error and usually results in 5xx status codes. +func TestBackendCall_NonParseableError(t *testing.T) { + errorResponse := `{invalid}` + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + _, err := w.Write([]byte(errorResponse)) + require.NoError(t, err) + })) + defer ts.Close() + + SetBackend(NewBackend(&BackendConfig{ + HTTPClient: ts.Client(), + URL: &ts.URL, + })) + resource := &testResource{} + err := GetBackend().Call(context.Background(), NewAPIRequest(http.MethodPost, "/resources"), resource) + require.Error(t, err) + // The raw error is returned since we cannot unmarshal it to a + // familiar API error response. + assert.Equal(t, errorResponse, err.Error()) +} diff --git a/docs/clerk-logo-dark.png b/docs/clerk-logo-dark.png deleted file mode 100644 index 1faeb941..00000000 Binary files a/docs/clerk-logo-dark.png and /dev/null differ diff --git a/docs/clerk-logo-light.png b/docs/clerk-logo-light.png deleted file mode 100644 index fe83c603..00000000 Binary files a/docs/clerk-logo-light.png and /dev/null differ diff --git a/examples/middleware/main.go b/examples/middleware/main.go deleted file mode 100644 index 7866340a..00000000 --- a/examples/middleware/main.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - - "github.com/clerkinc/clerk-sdk-go/clerk" -) - -func returnActiveSession(w http.ResponseWriter, req *http.Request) { - sessionClaims, ok := clerk.SessionFromContext(req.Context()) - if ok { - jsonResp, _ := json.Marshal(sessionClaims) - fmt.Fprintf(w, string(jsonResp)) - } else { - // handle non-authenticated user - } - -} - -func main() { - fmt.Print("Clerk secret key: ") - var apiKey string - fmt.Scanf("%s", &apiKey) - - client, err := clerk.NewClient(apiKey) - if err != nil { - panic(err) - } - - mux := http.NewServeMux() - injectActiveSession := clerk.WithSessionV2(client) - mux.Handle("/session", injectActiveSession(http.HandlerFunc(returnActiveSession))) - - err = http.ListenAndServe(":3000", mux) - log.Fatal(err) -} diff --git a/examples/operations/main.go b/examples/operations/main.go deleted file mode 100644 index 1cf241ad..00000000 --- a/examples/operations/main.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/clerkinc/clerk-sdk-go/clerk" -) - -func main() { - fmt.Print("Clerk API Key: ") - var apiKey string - fmt.Scanf("%s", &apiKey) - - client, err := clerk.NewClient(apiKey) - if err != nil { - panic(err) - } - - retrieveUsers(client) - retrieveSessions(client) -} - -func retrieveUsers(client clerk.Client) { - users, err := client.Users().ListAll(clerk.ListAllUsersParams{}) - if err != nil { - panic(err) - } - - fmt.Println("Users:") - for i, user := range users { - fmt.Printf("%v. %v %v\n", i+1, *user.FirstName, *user.LastName) - } -} - -func retrieveSessions(client clerk.Client) { - sessions, err := client.Sessions().ListAll() - if err != nil { - panic(err) - } - - fmt.Println("\nSessions:") - for i, session := range sessions { - fmt.Printf("%v. %v (%v)\n", i+1, session.ID, session.Status) - } -} diff --git a/go.mod b/go.mod index 7110e7aa..c05a2e5e 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ -module github.com/clerkinc/clerk-sdk-go +module github.com/clerk/clerk-sdk-go/v2 -go 1.16 +go 1.19 + +require github.com/stretchr/testify v1.8.2 require ( - github.com/brianvoe/gofakeit/v6 v6.19.0 - github.com/go-jose/go-jose/v3 v3.0.0 - github.com/google/go-cmp v0.5.6 // indirect - github.com/stretchr/testify v1.7.0 - golang.org/x/crypto v0.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2c160d8c..6a56e69b 100644 --- a/go.sum +++ b/go.sum @@ -1,52 +1,17 @@ -github.com/brianvoe/gofakeit/v6 v6.19.0 h1:g+yJ+meWVEsAmR+bV4mNM/eXI0N+0pZ3D+Mi+G5+YQo= -github.com/brianvoe/gofakeit/v6 v6.19.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= -github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/testdata/200x200-grayscale.jpg b/testdata/200x200-grayscale.jpg deleted file mode 100644 index 7d755bac..00000000 Binary files a/testdata/200x200-grayscale.jpg and /dev/null differ diff --git a/tests/integration/actor_tokens_test.go b/tests/integration/actor_tokens_test.go deleted file mode 100644 index 09c2f5be..00000000 --- a/tests/integration/actor_tokens_test.go +++ /dev/null @@ -1,56 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "fmt" - "testing" - "time" - - "github.com/clerkinc/clerk-sdk-go/clerk" - "github.com/stretchr/testify/assert" -) - -func TestActorTokens(t *testing.T) { - client := createClient() - - password := "my-extremely_Str0ng_P445wOrd" - firstName := "John" - lastName := "Doe" - user, err := client.Users().Create(clerk.CreateUserParams{ - EmailAddresses: []string{ - fmt.Sprintf("email_%d@example.com", time.Now().Unix()), - }, - Password: &password, - FirstName: &firstName, - LastName: &lastName, - }) - if err != nil { - t.Fatal(err) - } - - // create actor token - createParams := clerk.CreateActorTokenParams{ - UserID: user.ID, - Actor: []byte(`{"sub":"my_actor_id"}`), - } - actorTokenResponse, err := client.ActorTokens().Create(createParams) - if err != nil { - t.Fatal(err) - } - - assert.NotNil(t, actorTokenResponse.ID) - assert.Equal(t, "pending", actorTokenResponse.Status) - assert.JSONEq(t, string(createParams.Actor), string(actorTokenResponse.Actor)) - assert.Equal(t, createParams.UserID, actorTokenResponse.UserID) - - // revoke the previously created token - actorTokenResponse, err = client.ActorTokens().Revoke(actorTokenResponse.ID) - if err != nil { - t.Fatal(err) - } - - assert.Equal(t, "revoked", actorTokenResponse.Status) - assert.Empty(t, actorTokenResponse.Token) -} diff --git a/tests/integration/allowlists_test.go b/tests/integration/allowlists_test.go deleted file mode 100644 index bb3e5b3b..00000000 --- a/tests/integration/allowlists_test.go +++ /dev/null @@ -1,40 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "fmt" - "testing" - "time" - - "github.com/clerkinc/clerk-sdk-go/clerk" - "github.com/stretchr/testify/assert" -) - -func TestAllowlists(t *testing.T) { - client := createClient() - - allowlistIdentifiers, err := client.Allowlists().ListAllIdentifiers() - assert.Nil(t, err) - - previousCount := allowlistIdentifiers.TotalCount - - identifier := fmt.Sprintf("email_%d@example.com", time.Now().Unix()) - allowlistIdentifier, err := client.Allowlists().CreateIdentifier(clerk.CreateAllowlistIdentifierParams{ - Identifier: identifier, - }) - assert.Nil(t, err) - assert.NotEmpty(t, allowlistIdentifier.ID) - assert.Equal(t, identifier, allowlistIdentifier.Identifier) - assert.Equal(t, "allowlist_identifier", allowlistIdentifier.Object) - - allowlistIdentifiers, err = client.Allowlists().ListAllIdentifiers() - assert.Nil(t, err) - assert.Equal(t, previousCount+1, allowlistIdentifiers.TotalCount) - - deletedResponse, err := client.Allowlists().DeleteIdentifier(allowlistIdentifier.ID) - assert.Nil(t, err) - assert.Equal(t, allowlistIdentifier.ID, deletedResponse.ID) - assert.True(t, deletedResponse.Deleted) -} diff --git a/tests/integration/blocklists_test.go b/tests/integration/blocklists_test.go deleted file mode 100644 index d0bba10b..00000000 --- a/tests/integration/blocklists_test.go +++ /dev/null @@ -1,41 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "fmt" - "testing" - "time" - - "github.com/clerkinc/clerk-sdk-go/clerk" - "github.com/stretchr/testify/assert" -) - -func TestBlocklists(t *testing.T) { - client := createClient() - - blocklistIdentifiers, err := client.Blocklists().ListAllIdentifiers() - assert.Nil(t, err) - - previousCount := blocklistIdentifiers.TotalCount - - identifier := fmt.Sprintf("email_%d@example.com", time.Now().Unix()) - blocklistIdentifier, err := client.Blocklists().CreateIdentifier(clerk.CreateBlocklistIdentifierParams{ - Identifier: identifier, - }) - assert.Nil(t, err) - assert.NotEmpty(t, blocklistIdentifier.ID) - assert.Equal(t, identifier, blocklistIdentifier.Identifier) - assert.Equal(t, "email_address", blocklistIdentifier.IdentifierType) - assert.Equal(t, "blocklist_identifier", blocklistIdentifier.Object) - - blocklistIdentifiers, err = client.Blocklists().ListAllIdentifiers() - assert.Nil(t, err) - assert.Equal(t, previousCount+1, blocklistIdentifiers.TotalCount) - - deletedResponse, err := client.Blocklists().DeleteIdentifier(blocklistIdentifier.ID) - assert.Nil(t, err) - assert.Equal(t, blocklistIdentifier.ID, deletedResponse.ID) - assert.True(t, deletedResponse.Deleted) -} diff --git a/tests/integration/clerk_test.go b/tests/integration/clerk_test.go deleted file mode 100644 index 008d4ed6..00000000 --- a/tests/integration/clerk_test.go +++ /dev/null @@ -1,40 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "net/http" - "os" - "time" - - "github.com/clerkinc/clerk-sdk-go/clerk" -) - -type key string - -const ( - APIUrl = "CLERK_API_URL" - APIKey = "CLERK_API_KEY" -) - -func createClient() clerk.Client { - apiUrl := getEnv(APIUrl) - apiKey := getEnv(APIKey) - - httpClient := &http.Client{Timeout: time.Second * 20} - client, err := clerk.NewClient(apiKey, clerk.WithBaseURL(apiUrl), clerk.WithHTTPClient(httpClient)) - if err != nil { - panic("Unable to create Clerk client") - } - - return client -} - -func getEnv(k string) string { - envValue := os.Getenv(k) - if envValue == "" { - panic("Missing env variable " + k) - } - return envValue -} diff --git a/tests/integration/clients_test.go b/tests/integration/clients_test.go deleted file mode 100644 index f2d45583..00000000 --- a/tests/integration/clients_test.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "testing" -) - -func TestClients(t *testing.T) { - client := createClient() - - clients, err := client.Clients().ListAll() - if err != nil { - t.Fatalf("Clients.ListAll returned error: %v", err) - } - if clients == nil { - t.Fatalf("Clients.ListAll returned nil") - } - - for _, response := range clients { - if response.LastActiveSessionID == nil { - continue - } - clientId := response.ID - clientResponse, err := client.Clients().Read(clientId) - if err != nil { - t.Fatalf("Clients.Read returned error: %v", err) - } - if clientResponse == nil { - t.Fatalf("Clients.Read returned nil") - } - } -} diff --git a/tests/integration/domains_test.go b/tests/integration/domains_test.go deleted file mode 100644 index c8c42231..00000000 --- a/tests/integration/domains_test.go +++ /dev/null @@ -1,68 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "testing" - - "github.com/brianvoe/gofakeit/v6" - "github.com/clerkinc/clerk-sdk-go/clerk" - "github.com/stretchr/testify/assert" -) - -func TestDomains(t *testing.T) { - client := createClient() - - domains, err := client.Domains().ListAll() - assert.Nil(t, err) - - domainCount := domains.TotalCount - - // create (satellite) - - name := gofakeit.DomainName() - createDomainParams := clerk.CreateDomainParams{ - Name: name, - IsSatellite: true, - } - - domain, err := client.Domains().Create(createDomainParams) - assert.Nil(t, err) - - assert.Equal(t, "domain", domain.Object) - assert.Equal(t, name, domain.Name) - - // list - - domains, err = client.Domains().ListAll() - assert.Nil(t, err) - assert.Equal(t, domainCount+1, domains.TotalCount) - - // update - - name = gofakeit.DomainName() - updateDomainParams := clerk.UpdateDomainParams{ - Name: &name, - } - - domain, err = client.Domains().Update(domain.ID, updateDomainParams) - assert.Nil(t, err) - - assert.Equal(t, "domain", domain.Object) - assert.Equal(t, name, domain.Name) - - // delete - - deletedResponse, err := client.Domains().Delete(domain.ID) - assert.Nil(t, err) - - assert.Equal(t, domain.ID, deletedResponse.ID) - assert.True(t, deletedResponse.Deleted) - - // list - - domains, err = client.Domains().ListAll() - assert.Nil(t, err) - assert.Equal(t, domainCount, domains.TotalCount) -} diff --git a/tests/integration/email_addresses_test.go b/tests/integration/email_addresses_test.go deleted file mode 100644 index 8fa45cbc..00000000 --- a/tests/integration/email_addresses_test.go +++ /dev/null @@ -1,75 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "testing" - - "github.com/brianvoe/gofakeit/v6" - "github.com/clerkinc/clerk-sdk-go/clerk" - "github.com/stretchr/testify/assert" -) - -func TestEmailAddresses(t *testing.T) { - client := createClient() - - users, _ := client.Users().ListAll(clerk.ListAllUsersParams{}) - if users == nil || len(users) == 0 { - return - } - - user := users[0] - - // create - - fakeEmailAddress := gofakeit.Email() - - verified := false - primary := false - - createEmailAddressParams := clerk.CreateEmailAddressParams{ - UserID: user.ID, - EmailAddress: fakeEmailAddress, - Verified: &verified, - Primary: &primary, - } - - emailAddress, err := client.EmailAddresses().Create(createEmailAddressParams) - assert.Nil(t, err) - - assert.Equal(t, "email_address", emailAddress.Object) - assert.Equal(t, fakeEmailAddress, emailAddress.EmailAddress) - - // read - - emailAddress, err = client.EmailAddresses().Read(emailAddress.ID) - assert.Nil(t, err) - - assert.Equal(t, "email_address", emailAddress.Object) - assert.Equal(t, fakeEmailAddress, emailAddress.EmailAddress) - - // update - - verified = true - primary = true - - updateEmailAddressParams := clerk.UpdateEmailAddressParams{ - Verified: &verified, - Primary: &primary, - } - - emailAddress, err = client.EmailAddresses().Update(emailAddress.ID, updateEmailAddressParams) - assert.Nil(t, err) - - assert.Equal(t, "email_address", emailAddress.Object) - assert.Equal(t, fakeEmailAddress, emailAddress.EmailAddress) - - // delete - - deletedResponse, err := client.EmailAddresses().Delete(emailAddress.ID) - assert.Nil(t, err) - - assert.Equal(t, emailAddress.ID, deletedResponse.ID) - assert.True(t, deletedResponse.Deleted) -} diff --git a/tests/integration/emails_test.go b/tests/integration/emails_test.go deleted file mode 100644 index 142a8ba6..00000000 --- a/tests/integration/emails_test.go +++ /dev/null @@ -1,48 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/clerkinc/clerk-sdk-go/clerk" -) - -func TestEmails(t *testing.T) { - client := createClient() - - users, _ := client.Users().ListAll(clerk.ListAllUsersParams{}) - if users == nil || len(users) == 0 { - return - } - - user := users[0] - - if user.PrimaryEmailAddressID == nil { - return - } - - email := clerk.Email{ - FromEmailName: "integration-test", - Subject: "Testing Go SDK", - Body: "Testing email functionality for Go SDK", - EmailAddressID: *user.PrimaryEmailAddressID, - } - - emailResponse, err := client.Emails().Create(email) - if err != nil { - t.Fatalf("Emails.Create returned error: %v", err) - } - - assert.Equal(t, "email", emailResponse.Object) - assert.Equal(t, "queued", emailResponse.Status) - assert.Equal(t, email.FromEmailName, emailResponse.FromEmailName) - assert.Equal(t, email.EmailAddressID, emailResponse.EmailAddressID) - assert.Equal(t, email.Subject, emailResponse.Subject) - assert.Equal(t, email.Body, emailResponse.Body) - assert.True(t, emailResponse.DeliveredByClerk) - // assert.Nil(t, emailResponse.Data) -} diff --git a/tests/integration/instances_test.go b/tests/integration/instances_test.go deleted file mode 100644 index cbf066c4..00000000 --- a/tests/integration/instances_test.go +++ /dev/null @@ -1,57 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "testing" - - "github.com/clerkinc/clerk-sdk-go/clerk" - "github.com/stretchr/testify/assert" -) - -func TestInstances(t *testing.T) { - client := createClient() - - enabled := true - developmentOrigin := "http://localhost:3000" - supportEmail := "support@example.com" - err := client.Instances().Update(clerk.UpdateInstanceParams{ - TestMode: &enabled, - HIBP: &enabled, - EnhancedEmailDeliverability: &enabled, - SupportEmail: &supportEmail, - DevelopmentOrigin: &developmentOrigin, - }) - if err != nil { - t.Fatalf("Instances.Update returned error: %v", err) - } -} - -func TestInstanceRestrictions(t *testing.T) { - client := createClient() - - enabled := true - restrictionsResponse, err := client.Instances().UpdateRestrictions(clerk.UpdateRestrictionsParams{ - Allowlist: &enabled, - Blocklist: &enabled, - }) - assert.Nil(t, err) - assert.True(t, restrictionsResponse.Allowlist) - assert.True(t, restrictionsResponse.Blocklist) - assert.False(t, restrictionsResponse.BlockEmailSubaddresses) - assert.False(t, restrictionsResponse.BlockDisposableEmailDomains) -} - -func TestInstanceOrganizationSettings(t *testing.T) { - client := createClient() - - enabled := true - allowedMemberships := 0 - organizationSettingsResponse, err := client.Instances().UpdateOrganizationSettings(clerk.UpdateOrganizationSettingsParams{ - Enabled: &enabled, - MaxAllowedMemberships: &allowedMemberships, - }) - assert.Nil(t, err) - assert.True(t, organizationSettingsResponse.Enabled) -} diff --git a/tests/integration/jwt_templates_test.go b/tests/integration/jwt_templates_test.go deleted file mode 100644 index f09317de..00000000 --- a/tests/integration/jwt_templates_test.go +++ /dev/null @@ -1,78 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "encoding/json" - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "github.com/clerkinc/clerk-sdk-go/clerk" -) - -func TestJWTTemplates(t *testing.T) { - c := createClient() - - jwtTemplates, err := c.JWTTemplates().ListAll() - if err != nil { - t.Fatalf("JWTTemplates.ListAll returned error: %v", err) - } - if jwtTemplates == nil { - t.Fatalf("JWTTemplates.ListAll returned nil") - } - - for _, jwtTemplate := range jwtTemplates { - tmpl, err := c.JWTTemplates().Read(jwtTemplate.ID) - if err != nil { - t.Fatalf("JWTTemplates.Read returned error: %v", err) - } - if tmpl == nil { - t.Fatalf("JWTTemplates.Read returned nil") - } - } - - newJWTTemplate := &clerk.CreateUpdateJWTTemplate{ - Name: fmt.Sprintf("Integration-%d", time.Now().Unix()), - Claims: map[string]interface{}{ - "name": "{{user.first_name}}", - "role": "tester", - }, - } - - jwtTemplate, err := c.JWTTemplates().Create(newJWTTemplate) - if err != nil { - t.Fatalf("JWTTemplates.Create returned error: %v", err) - } - - assert.Equal(t, newJWTTemplate.Name, jwtTemplate.Name) - assert.Equal(t, 60, jwtTemplate.Lifetime) - assert.Equal(t, 5, jwtTemplate.AllowedClockSkew) - - updateJWTTemplate := &clerk.CreateUpdateJWTTemplate{ - Name: fmt.Sprintf("Updated-Integration-%d", time.Now().Unix()), - Claims: map[string]interface{}{ - "name": "{{user.first_name}}", - "age": 28, - }, - } - - updated, err := c.JWTTemplates().Update(jwtTemplate.ID, updateJWTTemplate) - if err != nil { - t.Fatalf("JWTTemplates.Create returned error: %v", err) - } - - assert.Equal(t, jwtTemplate.ID, updated.ID) - assert.Equal(t, updateJWTTemplate.Name, updated.Name) - - expectedClaimsBytes, _ := json.Marshal(updateJWTTemplate.Claims) - assert.JSONEq(t, string(expectedClaimsBytes), string(updated.Claims)) - - _, err = c.JWTTemplates().Delete(jwtTemplate.ID) - if err != nil { - t.Fatalf("JWTTemplates.Delete returned error: %v", err) - } -} diff --git a/tests/integration/organizations_test.go b/tests/integration/organizations_test.go deleted file mode 100644 index dfece538..00000000 --- a/tests/integration/organizations_test.go +++ /dev/null @@ -1,355 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "encoding/json" - "os" - "path" - "testing" - - "github.com/clerkinc/clerk-sdk-go/clerk" - "github.com/stretchr/testify/assert" -) - -type organizationMetadata struct { - AppID int `json:"app_id"` -} - -func TestOrganizations(t *testing.T) { - client := createClient() - - limit := 2 - users, err := client.Users().ListAll(clerk.ListAllUsersParams{ - Limit: &limit, - }) - if err != nil { - t.Fatalf("Users.ListAll returned error: %v", err) - } - if len(users) != 2 { - t.Fatalf("Users.ListAll returned %d results, expected 2", len(users)) - } - - // Delete all pre existing org before running the tests. - deleteAllOrgs, err := client.Organizations().ListAll(clerk.ListAllOrganizationsParams{ - IncludeMembersCount: true, - }) - for _, organization := range deleteAllOrgs.Data { - _, err := client.Organizations().Delete(organization.ID) - assert.NoError(t, err) - } - - newOrganization, err := client.Organizations().Create(clerk.CreateOrganizationParams{ - Name: "my-org", - CreatedBy: users[0].ID, - }) - if err != nil { - t.Fatal(err) - } - assert.NotEmpty(t, newOrganization.ID) - assert.Equal(t, "my-org", newOrganization.Name) - - membershipLimit := 20 - updatedOrganization, err := client.Organizations().Update(newOrganization.ID, clerk.UpdateOrganizationParams{ - MaxAllowedMemberships: &membershipLimit, - }) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, membershipLimit, updatedOrganization.MaxAllowedMemberships) - - privateMetadata, err := json.Marshal( - organizationMetadata{ - AppID: 6, - }, - ) - publicMetadata, err := json.Marshal( - organizationMetadata{ - AppID: 2, - }, - ) - updatedOrganization, err = client.Organizations().Update(newOrganization.ID, clerk.UpdateOrganizationParams{ - PrivateMetadata: privateMetadata, - PublicMetadata: publicMetadata, - }) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, updatedOrganization.PrivateMetadata, json.RawMessage(privateMetadata)) - assert.Equal(t, updatedOrganization.PublicMetadata, json.RawMessage(publicMetadata)) - - slug := "my-org-slug" - updatedOrganization, err = client.Organizations().Update(newOrganization.ID, clerk.UpdateOrganizationParams{ - Slug: &slug, - }) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, slug, *updatedOrganization.Slug) - - // Update organization logo - assert.Nil(t, updatedOrganization.LogoURL) - filename := "200x200-grayscale.jpg" - file, err := os.Open(path.Join("..", "..", "testdata", filename)) - if err != nil { - t.Fatal(err) - } - defer file.Close() - updatedOrganization, err = client.Organizations().UpdateLogo(updatedOrganization.ID, clerk.UpdateOrganizationLogoParams{ - File: file, - Filename: &filename, - UploaderUserID: users[0].ID, - }) - assert.NoError(t, err) - assert.NotNil(t, updatedOrganization.LogoURL) - - // Delete organization logo - updatedOrganization, err = client.Organizations().DeleteLogo(updatedOrganization.ID) - assert.NoError(t, err) - assert.Nil(t, updatedOrganization.LogoURL) - - organizations, err := client.Organizations().ListAll(clerk.ListAllOrganizationsParams{ - IncludeMembersCount: true, - }) - if err != nil { - t.Fatalf("Organizations.ListAll returned error: %v", err) - } - if organizations == nil { - t.Fatalf("Organizations.ListAll returned nil") - } - - assert.Greater(t, len(organizations.Data), 0) - assert.Greater(t, organizations.TotalCount, int64(0)) - for _, organization := range organizations.Data { - assert.Greater(t, *organization.MembersCount, 0) - } - - newOrganization2, err := client.Organizations().Create(clerk.CreateOrganizationParams{ - Name: "my-org-2", - CreatedBy: users[0].ID, - }) - if err != nil { - t.Fatal(err) - } - assert.NotEmpty(t, newOrganization2.ID) - assert.Equal(t, "my-org-2", newOrganization2.Name) - - // Should sort Organizations by name DESC - orderBy := "-name" - organizations, err = client.Organizations().ListAll(clerk.ListAllOrganizationsParams{ - OrderBy: &orderBy, - }) - assert.NoError(t, err) - assert.Equal(t, organizations.Data[0].ID, newOrganization2.ID) - - orderBy = "+name" - // Should short Organizations by name ASC - organizations, err = client.Organizations().ListAll(clerk.ListAllOrganizationsParams{ - OrderBy: &orderBy, - }) - assert.NoError(t, err) - assert.Equal(t, organizations.Data[0].ID, newOrganization.ID) - - // Should return 1 organization - exact match on ID - organizations, err = client.Organizations().ListAll(clerk.ListAllOrganizationsParams{ - Query: newOrganization.ID, - }) - - if err != nil { - t.Fatalf("Organizations.ListAll returned error: %v", err) - } - if organizations == nil { - t.Fatalf("Organizations.ListAll returned nil") - } - - assert.Equal(t, len(organizations.Data), 1) - assert.Equal(t, organizations.Data[0].ID, newOrganization.ID) - assert.Greater(t, organizations.TotalCount, int64(0)) - - // Should return organizations with name containing "my-org" - organizations, err = client.Organizations().ListAll(clerk.ListAllOrganizationsParams{ - Query: "my-org", - }) - - if err != nil { - t.Fatalf("Organizations.ListAll returned error: %v", err) - } - if organizations == nil { - t.Fatalf("Organizations.ListAll returned nil") - } - - assert.Greater(t, len(organizations.Data), 0) - assert.Greater(t, organizations.TotalCount, int64(0)) - for _, organization := range organizations.Data { - assert.Contains(t, organization.Name, "my-org") - } - - // Should return 2 results when using the userID as search param with ('+') prefix. - filteredOrganizationByUserID, err := client.Organizations().ListAll(clerk.ListAllOrganizationsParams{ - UserIDs: []string{"+" + users[0].ID}, - }) - assert.NoError(t, err) - - assert.Equal(t, len(filteredOrganizationByUserID.Data), 2) - assert.Equal(t, filteredOrganizationByUserID.TotalCount, int64(2)) - - // Should return non empty list - organizationMemberships, err := client.Organizations().ListMemberships(clerk.ListOrganizationMembershipsParams{ - OrganizationID: newOrganization.ID, - }) - if err != nil { - t.Fatalf("Organizations.ListMemberships returned error: %v", err) - } - if organizationMemberships == nil { - t.Fatalf("Organizations.ListMemberships returned nil") - } - assert.Greater(t, len(organizationMemberships.Data), 0) - assert.Greater(t, organizationMemberships.TotalCount, int64(0)) - for _, organizationMembership := range organizationMemberships.Data { - assert.NotEmpty(t, organizationMembership.ID) - } - - // Should return 1 result when providing matching query - organizationMembershipsWithQuery, err := client.Organizations().ListMemberships(clerk.ListOrganizationMembershipsParams{ - OrganizationID: newOrganization.ID, - Query: &organizationMemberships.Data[0].PublicUserData.UserID, - }) - if err != nil { - t.Fatalf("Organizations.ListMemberships with query returned error: %v", err) - } - if organizationMembershipsWithQuery == nil { - t.Fatalf("Organizations.ListMemberships with query returned nil") - } - assert.Equal(t, len(organizationMembershipsWithQuery.Data), 1) - assert.Equal(t, organizationMembershipsWithQuery.TotalCount, int64(1)) - - // Should return 0 results when providing non matching query - query := "somequery" - organizationMembershipsWithQuery, err = client.Organizations().ListMemberships(clerk.ListOrganizationMembershipsParams{ - OrganizationID: newOrganization.ID, - Query: &query, - }) - if err != nil { - t.Fatalf("Organizations.ListMemberships with query returned error: %v", err) - } - if organizationMembershipsWithQuery == nil { - t.Fatalf("Organizations.ListMemberships with query returned nil") - } - assert.Equal(t, len(organizationMembershipsWithQuery.Data), 0) - assert.Equal(t, organizationMembershipsWithQuery.TotalCount, int64(0)) - - // Should return 1 results when using the email as search param and email exist - organizationMembershipsWithQuery, err = client.Organizations().ListMemberships(clerk.ListOrganizationMembershipsParams{ - OrganizationID: newOrganization.ID, - EmailAddresses: []string{organizationMemberships.Data[0].PublicUserData.Identifier}, - }) - if err != nil { - t.Fatalf("Organizations.ListMemberships with query returned error: %v", err) - } - if organizationMembershipsWithQuery == nil { - t.Fatalf("Organizations.ListMemberships with query returned nil") - } - assert.Equal(t, len(organizationMembershipsWithQuery.Data), 1) - assert.Equal(t, organizationMembershipsWithQuery.TotalCount, int64(1)) - - // Should return 0 results when using the email as search param and email does not exist - organizationMembershipsWithQuery, err = client.Organizations().ListMemberships(clerk.ListOrganizationMembershipsParams{ - OrganizationID: newOrganization.ID, - EmailAddresses: []string{"justanemptyemai@clerk.com"}, - }) - if err != nil { - t.Fatalf("Organizations.ListMemberships with query returned error: %v", err) - } - if organizationMembershipsWithQuery == nil { - t.Fatalf("Organizations.ListMemberships with query returned nil") - } - assert.Equal(t, len(organizationMembershipsWithQuery.Data), 0) - assert.Equal(t, organizationMembershipsWithQuery.TotalCount, int64(0)) - - // Should change the role of a user to admin - createdOrganizationMembership, err := client.Organizations().CreateMembership(newOrganization.ID, clerk.CreateOrganizationMembershipParams{ - UserID: users[1].ID, - Role: "admin", - }) - if err != nil { - t.Fatalf("Organizations.CreateMembership returned error: %v", err) - } - if createdOrganizationMembership == nil { - t.Fatalf("Organizations.CreateMembership returned nil") - } - assert.Equal(t, createdOrganizationMembership.Role, "admin") - - // Should return 2 results when using Role admin as a query param - organizationMembershipsWithQuery, err = client.Organizations().ListMemberships(clerk.ListOrganizationMembershipsParams{ - OrganizationID: newOrganization.ID, - Roles: []string{"admin"}, - }) - if err != nil { - t.Fatalf("Organizations.ListMemberships with query returned error: %v", err) - } - if organizationMembershipsWithQuery == nil { - t.Fatalf("Organizations.ListMemberships with query returned nil") - } - assert.Equal(t, len(organizationMembershipsWithQuery.Data), 2) - assert.Equal(t, organizationMembershipsWithQuery.TotalCount, int64(2)) - - // Should change the role of a user to basic_member - updatedOrganizationMembership, err := client.Organizations().UpdateMembership(newOrganization.ID, clerk.UpdateOrganizationMembershipParams{ - UserID: organizationMemberships.Data[0].PublicUserData.UserID, - Role: "basic_member", - }) - if err != nil { - t.Fatalf("Organizations.UpdateMembership returned error: %v", err) - } - if updatedOrganizationMembership == nil { - t.Fatalf("Organizations.UpdateMembership returned nil") - } - assert.Equal(t, updatedOrganizationMembership.Role, "basic_member") - - // Should return 1 results when using Role admin as a query param - organizationMembershipsWithQuery, err = client.Organizations().ListMemberships(clerk.ListOrganizationMembershipsParams{ - OrganizationID: newOrganization.ID, - Roles: []string{"admin"}, - }) - if err != nil { - t.Fatalf("Organizations.ListMemberships with query returned error: %v", err) - } - if organizationMembershipsWithQuery == nil { - t.Fatalf("Organizations.ListMemberships with query returned nil") - } - assert.Equal(t, len(organizationMembershipsWithQuery.Data), 1) - assert.Equal(t, organizationMembershipsWithQuery.TotalCount, int64(1)) - - // Should delete member - deletedOrganizationMembership, err := client.Organizations().DeleteMembership(newOrganization.ID, - organizationMemberships.Data[0].PublicUserData.UserID, - ) - if err != nil { - t.Fatalf("Organizations.DeleteMembership returned error: %v", err) - } - if deletedOrganizationMembership == nil { - t.Fatalf("Organizations.DeleteMembership returned nil") - } - assert.Equal(t, deletedOrganizationMembership.Role, "basic_member") - - // Should return 0 result - afterDeletionMembershipsWithQuery, err := client.Organizations().ListMemberships(clerk.ListOrganizationMembershipsParams{ - OrganizationID: newOrganization.ID, - Query: &organizationMemberships.Data[0].PublicUserData.UserID, - }) - if err != nil { - t.Fatalf("Organizations.ListMemberships with query returned error: %v", err) - } - if afterDeletionMembershipsWithQuery == nil { - t.Fatalf("Organizations.ListMemberships with query returned nil") - } - assert.Equal(t, len(afterDeletionMembershipsWithQuery.Data), 0) - assert.Equal(t, afterDeletionMembershipsWithQuery.TotalCount, int64(0)) - - deleteResponse, err := client.Organizations().Delete(newOrganization.ID) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, newOrganization.ID, deleteResponse.ID) -} diff --git a/tests/integration/phone_numbers_test.go b/tests/integration/phone_numbers_test.go deleted file mode 100644 index 555c24d3..00000000 --- a/tests/integration/phone_numbers_test.go +++ /dev/null @@ -1,75 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "testing" - - "github.com/brianvoe/gofakeit/v6" - "github.com/clerkinc/clerk-sdk-go/clerk" - "github.com/stretchr/testify/assert" -) - -func TestPhoneNumbers(t *testing.T) { - client := createClient() - - users, _ := client.Users().ListAll(clerk.ListAllUsersParams{}) - if users == nil || len(users) == 0 { - return - } - - user := users[0] - - // create - - fakePhoneNumber := gofakeit.Phone() - - verified := false - primary := false - - createPhoneNumberParams := clerk.CreatePhoneNumberParams{ - UserID: user.ID, - PhoneNumber: fakePhoneNumber, - Verified: &verified, - Primary: &primary, - } - - phoneNumber, err := client.PhoneNumbers().Create(createPhoneNumberParams) - assert.Nil(t, err) - - assert.Equal(t, "phone_number", phoneNumber.Object) - assert.Equal(t, fakePhoneNumber, phoneNumber.PhoneNumber) - - // read - - phoneNumber, err = client.PhoneNumbers().Read(phoneNumber.ID) - assert.Nil(t, err) - - assert.Equal(t, "phone_number", phoneNumber.Object) - assert.Equal(t, fakePhoneNumber, phoneNumber.PhoneNumber) - - // update - - verified = true - primary = true - - updatePhoneNumberParams := clerk.UpdatePhoneNumberParams{ - Verified: &verified, - Primary: &primary, - } - - phoneNumber, err = client.PhoneNumbers().Update(phoneNumber.ID, updatePhoneNumberParams) - assert.Nil(t, err) - - assert.Equal(t, "phone_number", phoneNumber.Object) - assert.Equal(t, fakePhoneNumber, phoneNumber.PhoneNumber) - - // delete - - deletedResponse, err := client.PhoneNumbers().Delete(phoneNumber.ID) - assert.Nil(t, err) - - assert.Equal(t, phoneNumber.ID, deletedResponse.ID) - assert.True(t, deletedResponse.Deleted) -} diff --git a/tests/integration/proxy_checks_test.go b/tests/integration/proxy_checks_test.go deleted file mode 100644 index 549c314c..00000000 --- a/tests/integration/proxy_checks_test.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "testing" - - "github.com/brianvoe/gofakeit/v6" - "github.com/clerkinc/clerk-sdk-go/clerk" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestProxyChecks(t *testing.T) { - client := createClient() - // Create a domain first - domainName := gofakeit.DomainName() - domain, err := client.Domains().Create(clerk.CreateDomainParams{ - Name: domainName, - IsSatellite: true, - }) - require.NoError(t, err) - - // Now trigger a proxy check. Most likely a proxy is not configured. - _, err = client.ProxyChecks().Create(clerk.CreateProxyCheckParams{ - DomainID: domain.ID, - ProxyURL: "https://" + domainName + "/__clerk", - }) - assert.Error(t, err) -} diff --git a/tests/integration/redirect_urls_test.go b/tests/integration/redirect_urls_test.go deleted file mode 100644 index eaef5cb3..00000000 --- a/tests/integration/redirect_urls_test.go +++ /dev/null @@ -1,40 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "fmt" - "testing" - "time" - - "github.com/clerkinc/clerk-sdk-go/clerk" - "github.com/stretchr/testify/assert" -) - -func TestRedirectURLs(t *testing.T) { - client := createClient() - - redirectURLs, err := client.RedirectURLs().ListAll() - assert.Nil(t, err) - - previousRedirectURLsCount := len(redirectURLs) - - url := fmt.Sprintf("http://www.%d.com", time.Now().Unix()) - redirectURL, err := client.RedirectURLs().Create(clerk.CreateRedirectURLParams{ - URL: url, - }) - assert.Nil(t, err) - assert.NotEmpty(t, redirectURL.ID) - assert.Equal(t, url, redirectURL.URL) - assert.Equal(t, "redirect_url", redirectURL.Object) - - redirectURLs, err = client.RedirectURLs().ListAll() - assert.Nil(t, err) - assert.Equal(t, previousRedirectURLsCount+1, len(redirectURLs)) - - deletedResponse, err := client.RedirectURLs().Delete(redirectURL.ID) - assert.Nil(t, err) - assert.Equal(t, redirectURL.ID, deletedResponse.ID) - assert.True(t, deletedResponse.Deleted) -} diff --git a/tests/integration/sessions_test.go b/tests/integration/sessions_test.go deleted file mode 100644 index 16210659..00000000 --- a/tests/integration/sessions_test.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "testing" -) - -func TestSessions(t *testing.T) { - client := createClient() - - sessions, err := client.Sessions().ListAll() - if err != nil { - t.Fatalf("Sessions.ListAll returned error: %v", err) - } - if sessions == nil { - t.Fatalf("Sessions.ListAll returned nil") - } - - for _, session := range sessions { - sessionId := session.ID - session, err := client.Sessions().Read(sessionId) - if err != nil { - t.Fatalf("Sessions.Read returned error: %v", err) - } - if session == nil { - t.Fatalf("Sessions.Read returned nil") - } - } -} diff --git a/tests/integration/templates_test.go b/tests/integration/templates_test.go deleted file mode 100644 index ab608a0a..00000000 --- a/tests/integration/templates_test.go +++ /dev/null @@ -1,77 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "fmt" - "testing" - - "github.com/clerkinc/clerk-sdk-go/clerk" -) - -func TestTemplates(t *testing.T) { - client := createClient() - templateType := "email" - - // Get all email templates - templates, err := client.Templates().ListAll(templateType) - if err != nil { - t.Fatalf("Templates.ListAll returned error: %v", err) - } - if templates == nil { - t.Fatalf("Templates.ListAll returned nil") - } - - for _, template := range templates { - slug := template.Slug - - // Make sure we can read each template - tmpl, err := client.Templates().Read(templateType, slug) - if err != nil { - t.Fatalf("Templates.Read returned error: %v", err) - } - if tmpl == nil { - t.Fatalf("Templates.Read returned nil") - } - - // Preview each template with sample data - fromEmailName := "marketing" - templatePreview, err := client.Templates().Preview(templateType, slug, &clerk.PreviewTemplateRequest{ - Subject: "{{AppName}} is da bomb", - Body: "
{{AppName}} is the greatest app of all time!
", - FromEmailName: &fromEmailName, - }) - if err != nil { - t.Fatalf("Templates.Preview returned error: %v", err) - } - if templatePreview == nil { - t.Errorf("Templates.Preview returned nil") - } - } -} - -func TestTemplates_Upsert(t *testing.T) { - client := createClient() - - // Update one of the templates, just to make sure that the Upsert method works. - templateType := "email" - slug := "organization_invitation" - requiredVariable := "{{action_url}}" - deliveredByClerk := false - fromEmailName := "marketing" - upsertedTemplate, err := client.Templates().Upsert(templateType, slug, &clerk.UpsertTemplateRequest{ - Name: "Remarketing email", - Subject: "Unmissable opportunity", - Markup: "", - Body: fmt.Sprintf("Click %s for free unicorns", requiredVariable), - FromEmailName: &fromEmailName, - DeliveredByClerk: &deliveredByClerk, - }) - if err != nil { - t.Fatalf("Templates.Update returned error: %v", err) - } - if upsertedTemplate == nil { - t.Errorf("Templates.Upsert returned nil") - } -} diff --git a/tests/integration/users_test.go b/tests/integration/users_test.go deleted file mode 100644 index 0fe93326..00000000 --- a/tests/integration/users_test.go +++ /dev/null @@ -1,139 +0,0 @@ -//go:build integration -// +build integration - -package integration - -import ( - "encoding/json" - "testing" - - "github.com/clerkinc/clerk-sdk-go/clerk" - "github.com/stretchr/testify/assert" -) - -type addressDetails struct { - Street string `json:"street"` - Number string `json:"number"` -} - -type userAddress struct { - Address addressDetails `json:"address"` -} - -type userAppAndContactID struct { - AppID int `json:"app_id"` - ContactID int `json:"contact_id"` -} - -type userEvent struct { - ViewedProfile bool `json:"viewed_profile"` -} - -func TestUsers(t *testing.T) { - client := createClient() - - users, err := client.Users().ListAll(clerk.ListAllUsersParams{}) - if err != nil { - t.Fatalf("Users.ListAll returned error: %v", err) - } - if users == nil { - t.Fatalf("Users.ListAll returned nil") - } - - userCount, err := client.Users().Count(clerk.ListAllUsersParams{}) - if err != nil { - t.Fatalf("Users.Count returned error: %v", err) - } - if userCount.TotalCount == 0 { - t.Fatalf("Users.Count returned 0, expected %d", len(users)) - } - - for i, user := range users { - userId := user.ID - user, err := client.Users().Read(userId) - if err != nil { - t.Fatalf("Users.Read returned error: %v", err) - } - if user == nil { - t.Fatalf("Users.Read returned nil") - } - - updateRequest := clerk.UpdateUser{ - FirstName: user.FirstName, - LastName: user.LastName, - PublicMetadata: userAddress{Address: addressDetails{ - Street: "Fifth Avenue", - Number: "890", - }}, - PrivateMetadata: userAppAndContactID{AppID: i}, - UnsafeMetadata: userEvent{ - ViewedProfile: true, - }, - } - updatedUser, err := client.Users().Update(userId, &updateRequest) - if err != nil { - t.Fatalf("Users.Update returned error: %v", err) - } - if updatedUser == nil { - t.Errorf("Users.Update returned nil") - } - - privateMetadata := userAppAndContactID{ContactID: i} - privateMetadataJSON, _ := json.Marshal(privateMetadata) - - updatedUser, err = client.Users().UpdateMetadata(userId, &clerk.UpdateUserMetadata{ - PrivateMetadata: privateMetadataJSON, - }) - if err != nil { - t.Fatalf("Users.UpdateMetadata returned error: %v", err) - } - if updatedUser == nil { - t.Errorf("Users.UpdateMetadata returned nil") - } - - updatedUser, err = client.Users().Ban(userId) - if err != nil { - t.Fatalf("Users.Ban returned error: %v", err) - } - assert.True(t, updatedUser.Banned) - - updatedUser, err = client.Users().Unban(userId) - if err != nil { - t.Fatalf("Users.Unban returned error: %v", err) - } - assert.False(t, updatedUser.Banned) - - updatedUser, err = client.Users().Lock(userId) - if err != nil { - t.Fatalf("Users.Lock returned error: %v", err) - } - assert.True(t, updatedUser.Locked) - - updatedUser, err = client.Users().Unlock(userId) - if err != nil { - t.Fatalf("Users.Unlock returned error: %v", err) - } - assert.False(t, updatedUser.Locked) - } - - // Should return all memberships of a user - newOrganization, err := client.Organizations().Create(clerk.CreateOrganizationParams{ - Name: "my-org", - CreatedBy: users[0].ID, - }) - if err != nil { - t.Fatal(err) - } - - organizationMemberships, err := client.Users().ListMemberships(clerk.ListMembershipsParams{UserID: users[0].ID}) - assert.Equal(t, len(organizationMemberships.Data), 2) - assert.Equal(t, organizationMemberships.TotalCount, int64(2)) - assert.Equal(t, newOrganization.ID, organizationMemberships.Data[0].Organization.ID) - - // delete previous created organization to not create conflict with future tests - deleteResponse, err := client.Organizations().Delete(newOrganization.ID) - if err != nil { - t.Fatal(err) - } - assert.Equal(t, newOrganization.ID, deleteResponse.ID) -}