Skip to content

Commit

Permalink
Add environment roles API support.
Browse files Browse the repository at this point in the history
  • Loading branch information
mattgd committed Dec 18, 2024
1 parent 90d536f commit 1420d0b
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 3 deletions.
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

0 comments on commit 1420d0b

Please sign in to comment.