Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions pkg/roles/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# roles

[![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/roles)

A Go package to make requests to the WorkOS Roles API.

## Install

```sh
go get -u github.com/workos/workos-go/v4/pkg/roles
```

## How it works

See the [Roles and Permissions documentation](https://workos.com/docs/user-management/roles-and-permissions).
129 changes: 129 additions & 0 deletions pkg/roles/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package roles

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 Roles 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 Roles 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
}
}

// RoleType represents the type of a Role.
type RoleType string

// Constants that enumerate the type of a Role.
const (
Environment RoleType = "EnvironmentRole"
Organization RoleType = "OrganizationRole"
)

// Role contains data about a WorkOS Role.
type Role struct {
// The Role's unique identifier.
ID string `json:"id"`

Name string `json:"name"`

// The Role's slug key for referencing it in code.
Slug string `json:"slug"`

Description string `json:"description"`

// The type of role
Type RoleType `json:"type"`

// The timestamp of when the Role was created.
CreatedAt string `json:"created_at"`

// The timestamp of when the Role was updated.
UpdatedAt string `json:"updated_at"`
}

// ListRolesOpts contains the options to request Roles.
type ListRolesOpts struct{}

// ListRolesResponse describes the response structure when requesting Roles.
type ListRolesResponse struct {
// List of provisioned Roles.
Data []Role `json:"data"`
}

// ListRoles lists all roles in a WorkOS environment.
func (c *Client) ListRoles(
ctx context.Context,
opts ListRolesOpts,
) (ListRolesResponse, error) {
c.once.Do(c.init)

data, err := c.JSONEncode(opts)
if err != nil {
return ListRolesResponse{}, err
}

endpoint := fmt.Sprintf("%s/roles", c.Endpoint)
req, err := http.NewRequest(http.MethodGet, endpoint, bytes.NewBuffer(data))
if err != nil {
return ListRolesResponse{}, 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 ListRolesResponse{}, err
}
defer res.Body.Close()

if err = workos_errors.TryGetHTTPError(res); err != nil {
return ListRolesResponse{}, err
}

var body ListRolesResponse

dec := json.NewDecoder(res.Body)
err = dec.Decode(&body)
return body, err
}
98 changes: 98 additions & 0 deletions pkg/roles/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package roles

import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/require"
)

func TestListRoles(t *testing.T) {
tests := []struct {
scenario string
client *Client
options ListRolesOpts
expected ListRolesResponse
err bool
}{
{
scenario: "Request without API Key returns an error",
client: &Client{},
err: true,
},
{
scenario: "Request returns list of roles",
client: &Client{
APIKey: "test",
},
options: ListRolesOpts{},
expected: ListRolesResponse{
Data: []Role{
{
ID: "role_01EHWNCE74X7JSDV0X3SZ3KJNY",
Name: "Member",
Slug: "member",
Description: "The default role for all users.",
Type: Environment,
CreatedAt: "2024-12-01T00:00:00.000Z",
UpdatedAt: "2024-12-01T00:00:00.000Z",
},
},
},
},
}

for _, test := range tests {
t.Run(test.scenario, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(listRolesTestHandler))
defer server.Close()

client := test.client
client.Endpoint = server.URL
client.HTTPClient = server.Client()

response, err := client.ListRoles(context.Background(), test.options)
if test.err {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, test.expected, response)
})
}
}

func listRolesTestHandler(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 {
ListRolesResponse
}{ListRolesResponse{
Data: []Role{
{
ID: "role_01EHWNCE74X7JSDV0X3SZ3KJNY",
Name: "Member",
Slug: "member",
Description: "The default role for all users.",
Type: Environment,
CreatedAt: "2024-12-01T00:00:00.000Z",
UpdatedAt: "2024-12-01T00:00:00.000Z",
},
},
}})

if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
w.Write(body)
}
26 changes: 26 additions & 0 deletions pkg/roles/roles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Package `roles` provides a client wrapping the WorkOS Roles API.
package roles

import (
"context"
)

// DefaultClient is the client used by SetAPIKey and Roles functions.
var (
DefaultClient = &Client{
Endpoint: "https://api.workos.com",
}
)

// SetAPIKey sets the WorkOS API key for Roles API requests.
func SetAPIKey(apiKey string) {
DefaultClient.APIKey = apiKey
}

// ListRoles lists all Roles in an environment.
func ListRoles(
ctx context.Context,
opts ListRolesOpts,
) (ListRolesResponse, error) {
return DefaultClient.ListRoles(ctx, opts)
}
40 changes: 40 additions & 0 deletions pkg/roles/roles_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package roles

import (
"context"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/require"
)

func TestRolesListRoles(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(listRolesTestHandler))
defer server.Close()

DefaultClient = &Client{
HTTPClient: server.Client(),
Endpoint: server.URL,
}
SetAPIKey("test")

expectedResponse := ListRolesResponse{
Data: []Role{
{
ID: "role_01EHWNCE74X7JSDV0X3SZ3KJNY",
Name: "Member",
Slug: "member",
Description: "The default role for all users.",
Type: Environment,
CreatedAt: "2024-12-01T00:00:00.000Z",
UpdatedAt: "2024-12-01T00:00:00.000Z",
},
},
}

response, err := ListRoles(context.Background(), ListRolesOpts{})

require.NoError(t, err)
require.Equal(t, expectedResponse, response)
}
4 changes: 2 additions & 2 deletions pkg/widgets/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func TestGetToken(t *testing.T) {

for _, test := range tests {
t.Run(test.scenario, func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(generateLinkTestHandler))
server := httptest.NewServer(http.HandlerFunc(getTokenTestHandler))
defer server.Close()

client := test.client
Expand All @@ -57,7 +57,7 @@ func TestGetToken(t *testing.T) {
}
}

func generateLinkTestHandler(w http.ResponseWriter, r *http.Request) {
func getTokenTestHandler(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if auth != "Bearer test" {
http.Error(w, "bad auth", http.StatusUnauthorized)
Expand Down
2 changes: 1 addition & 1 deletion pkg/widgets/widgets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

func TestWidgetsGetToken(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(generateLinkTestHandler))
server := httptest.NewServer(http.HandlerFunc(getTokenTestHandler))
defer server.Close()

DefaultClient = &Client{
Expand Down
Loading