diff --git a/CHANGELOG.md b/CHANGELOG.md index 584dcda..05fe7a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.3.0 + +- Add support for the OAuth Applications API. Added the oauthapplication package for API operations and a clerk.OAuthApplication type. + ## 2.2.0 - Add support for bulk invitation creation with the `invitation.BulkCreate` method. diff --git a/oauth_application.go b/oauth_application.go new file mode 100644 index 0000000..c343216 --- /dev/null +++ b/oauth_application.go @@ -0,0 +1,25 @@ +package clerk + +type OAuthApplication struct { + APIResource + Object string `json:"object"` + ID string `json:"id"` + InstanceID string `json:"instance_id"` + Name string `json:"name"` + ClientID string `json:"client_id"` + Public bool `json:"public"` + Scopes string `json:"scopes"` + CallbackURL string `json:"callback_url"` + AuthorizeURL string `json:"authorize_url"` + TokenFetchURL string `json:"token_fetch_url"` + UserInfoURL string `json:"user_info_url"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` + ClientSecret *string `json:"client_secret,omitempty"` +} + +type OAuthApplicationList struct { + APIResource + OAuthApplications []*OAuthApplication `json:"data"` + TotalCount int64 `json:"total_count"` +} diff --git a/oauthapplication/api.go b/oauthapplication/api.go new file mode 100644 index 0000000..bb8bfc5 --- /dev/null +++ b/oauthapplication/api.go @@ -0,0 +1,45 @@ +// Code generated by "gen"; DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. +package oauthapplication + +import ( + "context" + + "github.com/clerk/clerk-sdk-go/v2" +) + +// Get fetches a single OAuth application by its ID. +func Get(ctx context.Context, id string) (*clerk.OAuthApplication, error) { + return getClient().Get(ctx, id) +} + +// List retrieves all OAuth applications. +func List(ctx context.Context, params *ListParams) (*clerk.OAuthApplicationList, error) { + return getClient().List(ctx, params) +} + +// Create creates a new OAuth application with the given parameters. +func Create(ctx context.Context, params *CreateParams) (*clerk.OAuthApplication, error) { + return getClient().Create(ctx, params) +} + +// Update updates an existing OAuth application. +func Update(ctx context.Context, id string, params *UpdateParams) (*clerk.OAuthApplication, error) { + return getClient().Update(ctx, id, params) +} + +// Delete deletes the given OAuth application +func DeleteOAuthApplication(ctx context.Context, id string) (*clerk.DeletedResource, error) { + return getClient().DeleteOAuthApplication(ctx, id) +} + +// RotateClientSecret rotates the OAuth application's client secret +func RotateClientSecret(ctx context.Context, id string) (*clerk.OAuthApplication, error) { + return getClient().RotateClientSecret(ctx, id) +} + +func getClient() *Client { + return &Client{ + Backend: clerk.GetBackend(), + } +} diff --git a/oauthapplication/client.go b/oauthapplication/client.go new file mode 100644 index 0000000..7d4c41b --- /dev/null +++ b/oauthapplication/client.go @@ -0,0 +1,115 @@ +// Package oauthapplication provides the OAuth applications API. +package oauthapplication + +import ( + "context" + "net/http" + "net/url" + + "github.com/clerk/clerk-sdk-go/v2" +) + +//go:generate go run ../cmd/gen/main.go + +const path = "/oauth_applications" + +type Client struct { + Backend clerk.Backend +} + +func NewClient(config *clerk.ClientConfig) *Client { + return &Client{ + Backend: clerk.NewBackend(&config.BackendConfig), + } +} + +// Get fetches a single OAuth application by its ID. +func (c *Client) Get(ctx context.Context, id string) (*clerk.OAuthApplication, error) { + path, err := clerk.JoinPath(path, id) + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodGet, path) + resource := &clerk.OAuthApplication{} + err = c.Backend.Call(ctx, req, resource) + return resource, err +} + +type ListParams struct { + clerk.APIParams + clerk.ListParams +} + +func (params *ListParams) ToQuery() url.Values { + return params.ListParams.ToQuery() +} + +// List retrieves all OAuth applications. +func (c *Client) List(ctx context.Context, params *ListParams) (*clerk.OAuthApplicationList, error) { + req := clerk.NewAPIRequest(http.MethodGet, path) + req.SetParams(params) + list := &clerk.OAuthApplicationList{} + err := c.Backend.Call(ctx, req, list) + return list, err +} + +type CreateParams struct { + clerk.APIParams + Name string `json:"name"` + CallbackURL string `json:"callback_url"` + Scopes string `json:"scopes"` + Public bool `json:"public"` +} + +// Create creates a new OAuth application with the given parameters. +func (c *Client) Create(ctx context.Context, params *CreateParams) (*clerk.OAuthApplication, error) { + req := clerk.NewAPIRequest(http.MethodPost, path) + req.SetParams(params) + authApplication := &clerk.OAuthApplication{} + err := c.Backend.Call(ctx, req, authApplication) + return authApplication, err +} + +type UpdateParams struct { + clerk.APIParams + Name *string `json:"name,omitempty"` + CallbackURL *string `json:"callback_url,omitempty"` + Scopes *string `json:"scopes,omitempty"` +} + +// Update updates an existing OAuth application. +func (c *Client) Update(ctx context.Context, id string, params *UpdateParams) (*clerk.OAuthApplication, error) { + path, err := clerk.JoinPath(path, id) + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodPatch, path) + req.SetParams(params) + authApplication := &clerk.OAuthApplication{} + err = c.Backend.Call(ctx, req, authApplication) + return authApplication, err +} + +// Delete deletes the given OAuth application +func (c *Client) DeleteOAuthApplication(ctx context.Context, id string) (*clerk.DeletedResource, error) { + path, err := clerk.JoinPath(path, id) + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodDelete, path) + authApplication := &clerk.DeletedResource{} + err = c.Backend.Call(ctx, req, authApplication) + return authApplication, err +} + +// RotateClientSecret rotates the OAuth application's client secret +func (c *Client) RotateClientSecret(ctx context.Context, id string) (*clerk.OAuthApplication, error) { + path, err := clerk.JoinPath(path, id, "rotate_secret") + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodPost, path) + authApplication := &clerk.OAuthApplication{} + err = c.Backend.Call(ctx, req, authApplication) + return authApplication, err +} diff --git a/oauthapplication/client_test.go b/oauthapplication/client_test.go new file mode 100644 index 0000000..a3b576c --- /dev/null +++ b/oauthapplication/client_test.go @@ -0,0 +1,226 @@ +package oauthapplication + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "testing" + + "github.com/clerk/clerk-sdk-go/v2" + "github.com/clerk/clerk-sdk-go/v2/clerktest" + "github.com/stretchr/testify/require" +) + +func TestOAuthApplicationClientCreate(t *testing.T) { + t.Parallel() + id := "oauth_app_123" + name := "Test Application" + callbackURL := "https://callback.url" + scopes := "read,write" + public := true + clientSecret := "secret_123" + + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(fmt.Sprintf(`{"name":"%s","callback_url":"https://callback.url","scopes":"read,write","public":true}`, name)), + Out: json.RawMessage(fmt.Sprintf(`{"id":"%s","name":"%s","callback_url":"%s","scopes":"%s","public":%t,"client_secret":"%s"}`, id, name, callbackURL, scopes, public, clientSecret)), + Method: http.MethodPost, + Path: "/v1/oauth_applications", + }, + } + client := NewClient(config) + + params := &CreateParams{ + Name: name, + CallbackURL: callbackURL, + Scopes: scopes, + Public: public, + } + oauthApp, err := client.Create(context.Background(), params) + require.NoError(t, err) + require.Equal(t, id, oauthApp.ID) + require.Equal(t, name, oauthApp.Name) + require.Equal(t, callbackURL, oauthApp.CallbackURL) + require.Equal(t, scopes, oauthApp.Scopes) + require.Equal(t, public, oauthApp.Public) + require.Equal(t, clientSecret, *oauthApp.ClientSecret) +} + +func TestOrganizationClientCreate_Error(t *testing.T) { + t.Parallel() + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Status: http.StatusBadRequest, + Out: json.RawMessage(`{ + "errors":[{ + "code":"create-error-code" + }], + "clerk_trace_id":"create-trace-id" +}`), + }, + } + client := NewClient(config) + _, err := client.Create(context.Background(), &CreateParams{}) + require.Error(t, err) + apiErr, ok := err.(*clerk.APIErrorResponse) + require.True(t, ok) + require.Equal(t, "create-trace-id", apiErr.TraceID) + require.Equal(t, 1, len(apiErr.Errors)) + require.Equal(t, "create-error-code", apiErr.Errors[0].Code) +} + +func TestOAuthApplicationClientGet(t *testing.T) { + t.Parallel() + id := "app_123" + name := "Test Application" + callbackURL := "https://callback.url" + scopes := "read,write" + public := true + + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(fmt.Sprintf(`{"id":"%s","name":"%s","callback_url":"%s","scopes":"%s","public":%t}`, id, name, callbackURL, scopes, public)), + Method: http.MethodGet, + Path: fmt.Sprintf("/v1/oauth_applications/%s", id), + }, + } + + client := NewClient(config) + oauthApp, err := client.Get(context.Background(), id) + require.NoError(t, err) + require.Equal(t, id, oauthApp.ID) + require.Equal(t, name, oauthApp.Name) + require.Equal(t, callbackURL, oauthApp.CallbackURL) + require.Equal(t, scopes, oauthApp.Scopes) + require.Equal(t, public, oauthApp.Public) +} + +func TestOAuthApplicationClientUpdate(t *testing.T) { + t.Parallel() + id := "app_123" + updatedName := "Updated Application" + callbackURL := "https://updated.callback.url" + + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + In: json.RawMessage(fmt.Sprintf(`{"name":"%s","callback_url":"%s"}`, updatedName, callbackURL)), + Out: json.RawMessage(fmt.Sprintf(`{"id":"%s","name":"%s","callback_url":"%s"}`, id, updatedName, callbackURL)), + Method: http.MethodPatch, + Path: fmt.Sprintf("/v1/oauth_applications/%s", id), + }, + } + + client := NewClient(config) + params := &UpdateParams{ + Name: clerk.String(updatedName), + CallbackURL: clerk.String(callbackURL), + } + oauthApp, err := client.Update(context.Background(), id, params) + require.NoError(t, err) + require.Equal(t, id, oauthApp.ID) + require.Equal(t, updatedName, oauthApp.Name) + require.Equal(t, callbackURL, oauthApp.CallbackURL) +} + +func TestOrganizationClientUpdate_Error(t *testing.T) { + t.Parallel() + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Status: http.StatusBadRequest, + Out: json.RawMessage(`{ + "errors":[{ + "code":"update-error-code" + }], + "clerk_trace_id":"update-trace-id" +}`), + }, + } + client := NewClient(config) + _, err := client.Update(context.Background(), "oauth_123", &UpdateParams{}) + require.Error(t, err) + apiErr, ok := err.(*clerk.APIErrorResponse) + require.True(t, ok) + require.Equal(t, "update-trace-id", apiErr.TraceID) + require.Equal(t, 1, len(apiErr.Errors)) + require.Equal(t, "update-error-code", apiErr.Errors[0].Code) +} + +func TestOAuthApplicationClientDelete(t *testing.T) { + t.Parallel() + id := "oauth_app_123" + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(fmt.Sprintf(`{"id":"%s","deleted":true}`, id)), + Method: http.MethodDelete, + Path: "/v1/oauth_applications/" + id, + }, + } + client := NewClient(config) + deletedApp, err := client.DeleteOAuthApplication(context.Background(), id) + require.NoError(t, err) + require.Equal(t, id, deletedApp.ID) + require.True(t, deletedApp.Deleted) +} + +func TestOAuthApplicationClientList(t *testing.T) { + t.Parallel() + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(`{ + "data": [{"id":"oauth_app_123","name":"OAuth App 1"}], + "total_count": 1 + }`), + Method: http.MethodGet, + Path: "/v1/oauth_applications", + Query: &url.Values{ + "limit": []string{"10"}, + "offset": []string{"0"}, + }, + }, + } + client := NewClient(config) + params := &ListParams{} + params.Limit = clerk.Int64(10) + params.Offset = clerk.Int64(0) + appList, err := client.List(context.Background(), params) + require.NoError(t, err) + require.Equal(t, int64(1), appList.TotalCount) + require.Equal(t, "oauth_app_123", appList.OAuthApplications[0].ID) + require.Equal(t, "OAuth App 1", appList.OAuthApplications[0].Name) +} + +func TestOAuthApplicationClientRotateClientSecret(t *testing.T) { + t.Parallel() + id := "oauth_app_123" + newSecret := "new_client_secret" + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(fmt.Sprintf(`{"id":"%s","client_secret":"%s"}`, id, newSecret)), + Method: http.MethodPost, + Path: "/v1/oauth_applications/" + id + "/rotate_secret", + }, + } + client := NewClient(config) + updatedApp, err := client.RotateClientSecret(context.Background(), id) + require.NoError(t, err) + require.Equal(t, id, updatedApp.ID) + require.Equal(t, newSecret, *updatedApp.ClientSecret) +}