From cfb60338fe1daa52692682975e001bffe8c1e1f1 Mon Sep 17 00:00:00 2001 From: Giannis Katsanos Date: Mon, 29 Jan 2024 11:15:09 +0200 Subject: [PATCH] chore: Fresh start Removed all files to prepare for a v2 rewrite. --- .github/workflows/tests.yml | 55 -- README.md | 108 +--- clerk/actor_tokens.go | 49 -- clerk/actor_tokens_test.go | 85 --- clerk/allowlists.go | 61 --- clerk/allowlists_test.go | 111 ---- clerk/blocklists.go | 58 --- clerk/blocklists_test.go | 110 ---- clerk/clerk.go | 381 -------------- clerk/clerk_options.go | 38 -- clerk/clerk_options_test.go | 56 -- clerk/clerk_test.go | 276 ---------- clerk/clients.go | 48 -- clerk/clients_test.go | 134 ----- clerk/delete_response.go | 8 - clerk/domains.go | 91 ---- clerk/domains_test.go | 142 ----- clerk/email_addresses.go | 72 --- clerk/email_addresses_test.go | 154 ------ clerk/emails.go | 35 -- clerk/emails_test.go | 57 --- clerk/errors.go | 24 - clerk/http_utils_test.go | 64 --- clerk/instances.go | 133 ----- clerk/instances_test.go | 151 ------ clerk/jwks.go | 23 - clerk/jwks_cache.go | 43 -- clerk/jwks_test.go | 58 --- clerk/jwt_templates.go | 143 ------ clerk/jwt_templates_test.go | 235 --------- clerk/middleware.go | 71 --- clerk/middleware_test.go | 213 -------- clerk/middleware_v2.go | 200 -------- clerk/middleware_v2_test.go | 53 -- clerk/organizations.go | 387 -------------- clerk/organizations_test.go | 343 ------------- clerk/phone_numbers.go | 75 --- clerk/phone_numbers_test.go | 160 ------ clerk/proxy_checks.go | 31 -- clerk/proxy_checks_test.go | 52 -- clerk/redirect_urls.go | 50 -- clerk/redirect_urls_test.go | 106 ---- clerk/saml_connections.go | 139 ----- clerk/saml_connections_test.go | 213 -------- clerk/session_claims.go | 38 -- clerk/session_claims_test.go | 31 -- clerk/sessions.go | 115 ----- clerk/sessions_test.go | 254 --------- clerk/templates.go | 154 ------ clerk/templates_test.go | 227 -------- clerk/tokens.go | 129 ----- clerk/tokens_issuer.go | 38 -- clerk/tokens_options.go | 96 ---- clerk/tokens_options_test.go | 110 ---- clerk/tokens_test.go | 447 ---------------- clerk/users.go | 360 ------------- clerk/users_test.go | 597 ---------------------- clerk/verification.go | 78 --- clerk/verification_test.go | 224 -------- clerk/web3_wallets.go | 8 - clerk/webhooks.go | 38 -- clerk/webhooks_test.go | 104 ---- docs/clerk-logo-dark.png | Bin 12638 -> 0 bytes docs/clerk-logo-light.png | Bin 4812 -> 0 bytes examples/middleware/main.go | 39 -- examples/operations/main.go | 45 -- go.mod | 10 +- go.sum | 52 -- testdata/200x200-grayscale.jpg | Bin 4757 -> 0 bytes tests/integration/actor_tokens_test.go | 56 -- tests/integration/allowlists_test.go | 40 -- tests/integration/blocklists_test.go | 41 -- tests/integration/clerk_test.go | 40 -- tests/integration/clients_test.go | 34 -- tests/integration/domains_test.go | 68 --- tests/integration/email_addresses_test.go | 75 --- tests/integration/emails_test.go | 48 -- tests/integration/instances_test.go | 57 --- tests/integration/jwt_templates_test.go | 78 --- tests/integration/organizations_test.go | 355 ------------- tests/integration/phone_numbers_test.go | 75 --- tests/integration/proxy_checks_test.go | 31 -- tests/integration/redirect_urls_test.go | 40 -- tests/integration/sessions_test.go | 31 -- tests/integration/templates_test.go | 77 --- tests/integration/users_test.go | 139 ----- 86 files changed, 7 insertions(+), 9568 deletions(-) delete mode 100644 .github/workflows/tests.yml delete mode 100644 clerk/actor_tokens.go delete mode 100644 clerk/actor_tokens_test.go delete mode 100644 clerk/allowlists.go delete mode 100644 clerk/allowlists_test.go delete mode 100644 clerk/blocklists.go delete mode 100644 clerk/blocklists_test.go delete mode 100644 clerk/clerk.go delete mode 100644 clerk/clerk_options.go delete mode 100644 clerk/clerk_options_test.go delete mode 100644 clerk/clerk_test.go delete mode 100644 clerk/clients.go delete mode 100644 clerk/clients_test.go delete mode 100644 clerk/delete_response.go delete mode 100644 clerk/domains.go delete mode 100644 clerk/domains_test.go delete mode 100644 clerk/email_addresses.go delete mode 100644 clerk/email_addresses_test.go delete mode 100644 clerk/emails.go delete mode 100644 clerk/emails_test.go delete mode 100644 clerk/errors.go delete mode 100644 clerk/http_utils_test.go delete mode 100644 clerk/instances.go delete mode 100644 clerk/instances_test.go delete mode 100644 clerk/jwks.go delete mode 100644 clerk/jwks_cache.go delete mode 100644 clerk/jwks_test.go delete mode 100644 clerk/jwt_templates.go delete mode 100644 clerk/jwt_templates_test.go delete mode 100644 clerk/middleware.go delete mode 100644 clerk/middleware_test.go delete mode 100644 clerk/middleware_v2.go delete mode 100644 clerk/middleware_v2_test.go delete mode 100644 clerk/organizations.go delete mode 100644 clerk/organizations_test.go delete mode 100644 clerk/phone_numbers.go delete mode 100644 clerk/phone_numbers_test.go delete mode 100644 clerk/proxy_checks.go delete mode 100644 clerk/proxy_checks_test.go delete mode 100644 clerk/redirect_urls.go delete mode 100644 clerk/redirect_urls_test.go delete mode 100644 clerk/saml_connections.go delete mode 100644 clerk/saml_connections_test.go delete mode 100644 clerk/session_claims.go delete mode 100644 clerk/session_claims_test.go delete mode 100644 clerk/sessions.go delete mode 100644 clerk/sessions_test.go delete mode 100644 clerk/templates.go delete mode 100644 clerk/templates_test.go delete mode 100644 clerk/tokens.go delete mode 100644 clerk/tokens_issuer.go delete mode 100644 clerk/tokens_options.go delete mode 100644 clerk/tokens_options_test.go delete mode 100644 clerk/tokens_test.go delete mode 100644 clerk/users.go delete mode 100644 clerk/users_test.go delete mode 100644 clerk/verification.go delete mode 100644 clerk/verification_test.go delete mode 100644 clerk/web3_wallets.go delete mode 100644 clerk/webhooks.go delete mode 100644 clerk/webhooks_test.go delete mode 100644 docs/clerk-logo-dark.png delete mode 100644 docs/clerk-logo-light.png delete mode 100644 examples/middleware/main.go delete mode 100644 examples/operations/main.go delete mode 100644 testdata/200x200-grayscale.jpg delete mode 100644 tests/integration/actor_tokens_test.go delete mode 100644 tests/integration/allowlists_test.go delete mode 100644 tests/integration/blocklists_test.go delete mode 100644 tests/integration/clerk_test.go delete mode 100644 tests/integration/clients_test.go delete mode 100644 tests/integration/domains_test.go delete mode 100644 tests/integration/email_addresses_test.go delete mode 100644 tests/integration/emails_test.go delete mode 100644 tests/integration/instances_test.go delete mode 100644 tests/integration/jwt_templates_test.go delete mode 100644 tests/integration/organizations_test.go delete mode 100644 tests/integration/phone_numbers_test.go delete mode 100644 tests/integration/proxy_checks_test.go delete mode 100644 tests/integration/redirect_urls_test.go delete mode 100644 tests/integration/sessions_test.go delete mode 100644 tests/integration/templates_test.go delete mode 100644 tests/integration/users_test.go 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..59c57af6 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,18 @@ # Clerk Go SDK -Go client library for accessing the [Clerk Backend API](https://clerk.com/docs/reference/backend-api). +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/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) +[![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/clerk/clerk-sdk-go/clerk/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 +29,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/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 12c2cca5..00000000 --- a/clerk/saml_connections.go +++ /dev/null @@ -1,139 +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"` - 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 f0234c6a..00000000 --- a/clerk/saml_connections_test.go +++ /dev/null @@ -1,213 +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", - 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/docs/clerk-logo-dark.png b/docs/clerk-logo-dark.png deleted file mode 100644 index 1faeb9411dad12afcd0da917ef64b220278200c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12638 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 M07*qoM6N<$g1b0y9RL6T diff --git a/docs/clerk-logo-light.png b/docs/clerk-logo-light.png deleted file mode 100644 index fe83c603236421132e8c7acedc843aa16f5390bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4812 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

  • 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 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) -}