Skip to content
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 permissions API support #385

Draft
wants to merge 1 commit into
base: hackweek-typegen
Choose a base branch
from
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/permissions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# permissions

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

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

## Install

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

## How it works

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

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"sync"
"time"

"github.com/workos/workos-go/v4/pkg/common"
"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

// Order represents the order of records.
type Order string

// Constants that enumerate the available orders.
const (
Asc Order = "asc"
Desc Order = "desc"
)

// Client represents a client that performs Permissions 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 Permissions 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
}
}

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

Name string `json:"name"`

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

Description string `json:"description"`

// Whether this Permission is a system permission.
System bool `json:"system"`

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

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

// ListPermissionsOpts contains the options to request Permissions.
type ListPermissionsOpts struct {
// Maximum number of records to return.
Limit int `url:"limit,omitempty"`

// The order in which to paginate records.
Order Order `url:"order,omitempty"`

// Pagination cursor to receive records before a provided Organization ID.
Before string `url:"before,omitempty"`

// Pagination cursor to receive records after a provided Organization ID.
After string `url:"after,omitempty"`
}

// ListPermissionsResponse describes the response structure when requesting Permissions
type ListPermissionsResponse struct {
// List of provisioned Permissions.
Data []Permission `json:"data"`

// Cursor pagination options.
ListMetadata common.ListMetadata `json:"listMetadata"`
}

// ListPermissions lists all permissions in a WorkOS environment.
func (c *Client) ListPermissions(
ctx context.Context,
opts ListPermissionsOpts,
) (ListPermissionsResponse, error) {
c.once.Do(c.init)

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

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

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

var body ListPermissionsResponse

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

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

"github.com/stretchr/testify/require"
"github.com/workos/workos-go/v4/pkg/common"
)

func TestListPermissions(t *testing.T) {
tests := []struct {
scenario string
client *Client
options ListPermissionsOpts
expected ListPermissionsResponse
err bool
}{
{
scenario: "Request without API Key returns an error",
client: &Client{},
err: true,
},
{
scenario: "Request returns list of permissions",
client: &Client{
APIKey: "test",
},
options: ListPermissionsOpts{},
expected: ListPermissionsResponse{
Data: []Permission{
{
ID: "permission_01EHWNCE74X7JSDV0X3SZ3KJNY",
Name: "Manage users",
Slug: "users:manage",
Description: "Manage users in the application.",
System: false,
CreatedAt: "2024-12-01T00:00:00.000Z",
UpdatedAt: "2024-12-01T00:00:00.000Z",
},
},
ListMetadata: common.ListMetadata{
Before: "",
After: "",
},
},
},
}

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

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

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

func listPermissionsTestHandler(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 {
ListPermissionsResponse
}{ListPermissionsResponse{
Data: []Permission{
{
ID: "permission_01EHWNCE74X7JSDV0X3SZ3KJNY",
Name: "Manage users",
Slug: "users:manage",
Description: "Manage users in the application.",
System: false,
CreatedAt: "2024-12-01T00:00:00.000Z",
UpdatedAt: "2024-12-01T00:00:00.000Z",
},
},
ListMetadata: common.ListMetadata{
Before: "",
After: "",
},
}})

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

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

import (
"context"
)

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

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

// ListPermissions lists all Permissions in an environment.
func ListPermissions(
ctx context.Context,
opts ListPermissionsOpts,
) (ListPermissionsResponse, error) {
return DefaultClient.ListPermissions(ctx, opts)
}
45 changes: 45 additions & 0 deletions pkg/permissions/permissions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package permissions

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

"github.com/stretchr/testify/require"
"github.com/workos/workos-go/v4/pkg/common"
)

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

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

expectedResponse := ListPermissionsResponse{
Data: []Permission{
{
ID: "permission_01EHWNCE74X7JSDV0X3SZ3KJNY",
Name: "Manage users",
Slug: "users:manage",
Description: "Manage users in the application.",
System: false,
CreatedAt: "2024-12-01T00:00:00.000Z",
UpdatedAt: "2024-12-01T00:00:00.000Z",
},
},
ListMetadata: common.ListMetadata{
Before: "",
After: "",
},
}

response, err := ListPermissions(context.Background(), ListPermissionsOpts{})

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