-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add widgets client and get token API support.
- Loading branch information
Showing
5 changed files
with
266 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
|
||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |