-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add widgets client and get token API support #378
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# widgets | ||
|
||
[![Go Report Card](https://img.shields.io/badge/dev-reference-007d9c?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/workos/workos-go/v4/pkg/widgets) | ||
|
||
A Go package to make requests to the WorkOS Widgets API. | ||
|
||
## Install | ||
|
||
```sh | ||
go get -u github.com/workos/workos-go/v4/pkg/widgets | ||
``` | ||
|
||
## How it works | ||
|
||
See the [Widgets integration guide](https://workos.com/docs/widgets/guide). | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package widgets | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"sync" | ||
"time" | ||
|
||
"github.com/workos/workos-go/v4/pkg/workos_errors" | ||
|
||
"github.com/workos/workos-go/v4/internal/workos" | ||
) | ||
|
||
// ResponseLimit is the default number of records to limit a response to. | ||
const ResponseLimit = 10 | ||
|
||
// Client represents a client that performs Widgets requests to the WorkOS API. | ||
type Client struct { | ||
// The WorkOS API Key. It can be found in https://dashboard.workos.com/api-keys. | ||
APIKey string | ||
|
||
// The http.Client that is used to manage Widgets API calls to WorkOS. | ||
// Defaults to http.Client. | ||
HTTPClient *http.Client | ||
|
||
// The endpoint to WorkOS API. Defaults to https://api.workos.com. | ||
Endpoint string | ||
|
||
// The function used to encode in JSON. Defaults to json.Marshal. | ||
JSONEncode func(v interface{}) ([]byte, error) | ||
|
||
once sync.Once | ||
} | ||
|
||
func (c *Client) init() { | ||
if c.HTTPClient == nil { | ||
c.HTTPClient = &http.Client{Timeout: 10 * time.Second} | ||
} | ||
|
||
if c.Endpoint == "" { | ||
c.Endpoint = "https://api.workos.com" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should ideally be configurable. You can't currently change the API host in the golang SDK, but it's something we should add and that should reflect here too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great point. I also feel like it's less than ideal that we have to redefine this common client logic for every module. Wondering if it should be refactored into a shared module. |
||
} | ||
|
||
if c.JSONEncode == nil { | ||
c.JSONEncode = json.Marshal | ||
} | ||
} | ||
|
||
// WidgetScope represents a widget token scope. | ||
type WidgetScope string | ||
|
||
// Constants that enumerate the available GenerateLinkIntent types. | ||
const ( | ||
UsersTableManage WidgetScope = "widgets:users-table:manage" | ||
) | ||
|
||
// GetTokenOpts contains the options to get a widget token. | ||
type GetTokenOpts struct { | ||
// Organization identifier to scope the widget token | ||
OrganizationId string `json:"organization_id"` | ||
|
||
// AuthKit user identifier to scope the widget token | ||
UserId string `json:"user_id"` | ||
|
||
// WidgetScopes to scope the widget token | ||
Scopes []WidgetScope `json:"scopes"` | ||
} | ||
|
||
// GetTokenResponse represents the generated widget token | ||
type GetTokenResponse struct { | ||
// Generated widget token | ||
Token string `json:"token"` | ||
} | ||
|
||
// GetToken generates a widget token based on the provided options. | ||
func (c *Client) GetToken( | ||
ctx context.Context, | ||
opts GetTokenOpts, | ||
) (string, error) { | ||
c.once.Do(c.init) | ||
|
||
data, err := c.JSONEncode(opts) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
endpoint := fmt.Sprintf("%s/widgets/token", c.Endpoint) | ||
req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(data)) | ||
if err != nil { | ||
return "", err | ||
} | ||
req = req.WithContext(ctx) | ||
req.Header.Set("Content-Type", "application/json") | ||
req.Header.Set("Authorization", "Bearer "+c.APIKey) | ||
req.Header.Set("User-Agent", "workos-go/"+workos.Version) | ||
|
||
res, err := c.HTTPClient.Do(req) | ||
if err != nil { | ||
return "", err | ||
} | ||
defer res.Body.Close() | ||
|
||
if err = workos_errors.TryGetHTTPError(res); err != nil { | ||
return "", err | ||
} | ||
|
||
var body GetTokenResponse | ||
|
||
dec := json.NewDecoder(res.Body) | ||
err = dec.Decode(&body) | ||
return body.Token, err | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package widgets | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestGetToken(t *testing.T) { | ||
tests := []struct { | ||
scenario string | ||
client *Client | ||
options GetTokenOpts | ||
expected string | ||
err bool | ||
}{ | ||
{ | ||
scenario: "Request without API Key returns an error", | ||
client: &Client{}, | ||
err: true, | ||
}, | ||
{ | ||
scenario: "Request returns widget token", | ||
client: &Client{ | ||
APIKey: "test", | ||
}, | ||
options: GetTokenOpts{ | ||
OrganizationId: "organization_id", | ||
UserId: "user_id", | ||
Scopes: []WidgetScope{UsersTableManage}, | ||
}, | ||
expected: "abc123456", | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.scenario, func(t *testing.T) { | ||
server := httptest.NewServer(http.HandlerFunc(generateLinkTestHandler)) | ||
defer server.Close() | ||
|
||
client := test.client | ||
client.Endpoint = server.URL | ||
client.HTTPClient = server.Client() | ||
|
||
token, err := client.GetToken(context.Background(), test.options) | ||
if test.err { | ||
require.Error(t, err) | ||
return | ||
} | ||
require.NoError(t, err) | ||
require.Equal(t, test.expected, token) | ||
}) | ||
} | ||
} | ||
|
||
func generateLinkTestHandler(w http.ResponseWriter, r *http.Request) { | ||
auth := r.Header.Get("Authorization") | ||
if auth != "Bearer test" { | ||
http.Error(w, "bad auth", http.StatusUnauthorized) | ||
return | ||
} | ||
|
||
body, err := json.Marshal(struct { | ||
GetTokenResponse | ||
}{GetTokenResponse{Token: "abc123456"}}) | ||
|
||
if err != nil { | ||
w.WriteHeader(http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
w.WriteHeader(http.StatusOK) | ||
w.Write(body) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Package `widgets` provides a client wrapping the WorkOS Widgets API. | ||
package widgets | ||
|
||
import ( | ||
"context" | ||
) | ||
|
||
// DefaultClient is the client used by SetAPIKey and Widgets functions. | ||
var ( | ||
DefaultClient = &Client{ | ||
Endpoint: "https://api.workos.com", | ||
} | ||
) | ||
|
||
// SetAPIKey sets the WorkOS API key for Widgets API requests. | ||
func SetAPIKey(apiKey string) { | ||
DefaultClient.APIKey = apiKey | ||
} | ||
|
||
// GetToken generates an ephemeral widget token based on the provided options. | ||
func GetToken( | ||
ctx context.Context, | ||
opts GetTokenOpts, | ||
) (string, error) { | ||
return DefaultClient.GetToken(ctx, opts) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package widgets | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestWidgetsGetToken(t *testing.T) { | ||
server := httptest.NewServer(http.HandlerFunc(generateLinkTestHandler)) | ||
defer server.Close() | ||
|
||
DefaultClient = &Client{ | ||
HTTPClient: server.Client(), | ||
Endpoint: server.URL, | ||
} | ||
SetAPIKey("test") | ||
|
||
expectedToken := "abc123456" | ||
|
||
token, err := GetToken(context.Background(), GetTokenOpts{ | ||
OrganizationId: "organization_id", | ||
UserId: "user_id", | ||
Scopes: []WidgetScope{UsersTableManage}, | ||
}) | ||
|
||
require.NoError(t, err) | ||
require.Equal(t, expectedToken, token) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This page doesn't exist yet and should be updated before merging.