Skip to content
Closed
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
47 changes: 17 additions & 30 deletions go/apps/api/routes/v2_apis_create_api/401_test.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,32 @@
package handler_test

import (
"net/http"
"testing"

"github.com/stretchr/testify/require"
handler "github.com/unkeyed/unkey/go/apps/api/routes/v2_apis_create_api"
"github.com/unkeyed/unkey/go/pkg/testutil"
"github.com/unkeyed/unkey/go/pkg/testutil/authz"
"github.com/unkeyed/unkey/go/pkg/zen"
)

// TestCreateApi_Unauthorized verifies that API creation requests are properly
// rejected when authentication fails. This test ensures that invalid or missing
// authorization tokens result in 401 Unauthorized responses, preventing
// unauthorized access to the API creation endpoint.
func TestCreateApi_Unauthorized(t *testing.T) {
h := testutil.NewHarness(t)

route := &handler.Handler{
Logger: h.Logger,
DB: h.DB,
Keys: h.Keys,
Auditlogs: h.Auditlogs,
}

h.Register(route)

// This test validates that requests with malformed or invalid authorization
// tokens are rejected with a 401 status code, ensuring proper security
// boundaries for the API creation endpoint.
t.Run("invalid auth token", func(t *testing.T) {
headers := http.Header{
"Content-Type": {"application/json"},
"Authorization": {"Bearer invalid_token"},
}

req := handler.Request{
Name: "test-api",
}

res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req)
require.Equal(t, http.StatusUnauthorized, res.Status, "expected 401, sent: %+v, received: %s", req, res.RawBody)
})

authz.Test401[handler.Request, handler.Response](t,
func(h *testutil.Harness) zen.Route {
return &handler.Handler{
Logger: h.Logger,
DB: h.DB,
Keys: h.Keys,
Auditlogs: h.Auditlogs,
}
},
func() handler.Request {
return handler.Request{
Name: "test-api",
}
},
)
}
97 changes: 18 additions & 79 deletions go/apps/api/routes/v2_apis_create_api/403_test.go
Original file line number Diff line number Diff line change
@@ -1,96 +1,35 @@
package handler_test

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

"github.com/stretchr/testify/require"
handler "github.com/unkeyed/unkey/go/apps/api/routes/v2_apis_create_api"
"github.com/unkeyed/unkey/go/pkg/db"
"github.com/unkeyed/unkey/go/pkg/testutil"
"github.com/unkeyed/unkey/go/pkg/testutil/authz"
"github.com/unkeyed/unkey/go/pkg/zen"
)

// TestCreateApi_Forbidden verifies that API creation requests are properly
// rejected when the authenticated user lacks the required permissions. This test
// ensures that RBAC (Role-Based Access Control) is correctly enforced and that
// users without api.*.create_api permission receive 403 Forbidden responses.
func TestCreateApi_Forbidden(t *testing.T) {
h := testutil.NewHarness(t)

route := &handler.Handler{
Logger: h.Logger,
DB: h.DB,
Keys: h.Keys,
Auditlogs: h.Auditlogs,
}

h.Register(route)

// Create a root key with insufficient permissions
rootKey := h.CreateRootKey(h.Resources().UserWorkspace.ID, "identity.*.create_identity") // Not api.*.create_api
headers := http.Header{
"Content-Type": {"application/json"},
"Authorization": {fmt.Sprintf("Bearer %s", rootKey)},
}

// This test validates that a root key with valid authentication but
// insufficient permissions (lacking api.*.create_api) is properly rejected
// with a 403 status code, ensuring permission boundaries are enforced.
t.Run("insufficient permissions", func(t *testing.T) {
req := handler.Request{
Name: "test-api",
}

res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req)
require.Equal(t, http.StatusForbidden, res.Status)
})

// This test validates various permission combinations to ensure that only
// root keys with the exact api.*.create_api permission can create APIs, while
// keys with other permissions or insufficient permissions are rejected.
t.Run("permission combinations", func(t *testing.T) {
testCases := []struct {
name string
permissions []string
shouldPass bool
}{
{name: "specific permission", permissions: []string{"api.*.create_api"}, shouldPass: true},
{name: "specific permission and more", permissions: []string{"some.other.permission", "xxx", "api.*.create_api", "another.permission"}, shouldPass: true},
{name: "insufficient permission", permissions: []string{"api.*.read_api"}, shouldPass: false},
{name: "unrelated permission", permissions: []string{"identity.*.create_identity"}, shouldPass: false},
}

// Each test case validates a specific permission scenario to ensure
// proper RBAC enforcement across different permission combinations.
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create a root key with the specific permissions
permRootKey := h.CreateRootKey(h.Resources().UserWorkspace.ID, tc.permissions...)
permHeaders := http.Header{
"Content-Type": {"application/json"},
"Authorization": {fmt.Sprintf("Bearer %s", permRootKey)},
}

req := handler.Request{
Name: "test-api-permissions",
authz.Test403(t,
authz.PermissionTestConfig[handler.Request, handler.Response]{
SetupHandler: func(h *testutil.Harness) zen.Route {
return &handler.Handler{
Logger: h.Logger,
DB: h.DB,
Keys: h.Keys,
Auditlogs: h.Auditlogs,
}

res := testutil.CallRoute[handler.Request, handler.Response](h, route, permHeaders, req)

if tc.shouldPass {
require.Equal(t, 200, res.Status, "Expected 200 for permission: %v, got: %s", tc.permissions, res.RawBody)
require.NotEmpty(t, res.Body.Data.ApiId)

// Verify the API in the database
api, err := db.Query.FindApiByID(context.Background(), h.DB.RO(), res.Body.Data.ApiId)
require.NoError(t, err)
require.Equal(t, req.Name, api.Name)
} else {
require.Equal(t, http.StatusForbidden, res.Status, "Expected 403 for permission: %v, got: %s", tc.permissions, res.RawBody)
},
RequiredPermissions: []string{"api.*.create_api"},
CreateRequest: func(res authz.TestResources) handler.Request {
return handler.Request{
Name: "test-api",
}
})
}
})
},
},
)
}
98 changes: 19 additions & 79 deletions go/apps/api/routes/v2_apis_delete_api/401_test.go
Original file line number Diff line number Diff line change
@@ -1,90 +1,30 @@
package handler_test

import (
"net/http"
"testing"

"github.com/stretchr/testify/require"
"github.com/unkeyed/unkey/go/apps/api/openapi"
handler "github.com/unkeyed/unkey/go/apps/api/routes/v2_apis_delete_api"
"github.com/unkeyed/unkey/go/pkg/testutil"
"github.com/unkeyed/unkey/go/pkg/testutil/authz"
"github.com/unkeyed/unkey/go/pkg/uid"
"github.com/unkeyed/unkey/go/pkg/zen"
)

func TestAuthenticationErrors(t *testing.T) {
h := testutil.NewHarness(t)

route := &handler.Handler{
Logger: h.Logger,
DB: h.DB,
Keys: h.Keys,
Auditlogs: h.Auditlogs,
Caches: h.Caches,
}

h.Register(route)

// Create a valid request
req := handler.Request{
ApiId: "api_1234",
}

// Test case for missing authorization header
t.Run("missing authorization header", func(t *testing.T) {
// No Authorization header
headers := http.Header{
"Content-Type": {"application/json"},
}

res := testutil.CallRoute[handler.Request, openapi.UnauthorizedErrorResponse](
h,
route,
headers,
req,
)

require.Equal(t, 400, res.Status)
require.NotNil(t, res.Body)
require.NotNil(t, res.Body.Error)
require.Equal(t, "Authorization header for 'bearer' scheme", res.Body.Error.Detail)
})

// Test case for invalid authorization token
t.Run("invalid authorization token", func(t *testing.T) {
headers := http.Header{
"Content-Type": {"application/json"},
"Authorization": {"Bearer invalid_token_that_does_not_exist"},
}

res := testutil.CallRoute[handler.Request, openapi.UnauthorizedErrorResponse](
h,
route,
headers,
req,
)

require.Equal(t, 401, res.Status)
require.NotNil(t, res.Body)
require.NotNil(t, res.Body.Error)
require.Equal(t, "The provided root key is invalid. We could not find the requested key.", res.Body.Error.Detail)
})

// Test case for malformed authorization header
t.Run("malformed authorization header", func(t *testing.T) {
headers := http.Header{
"Content-Type": {"application/json"},
"Authorization": {"malformed_header_without_bearer_prefix"},
}

res := testutil.CallRoute[handler.Request, openapi.UnauthorizedErrorResponse](
h,
route,
headers,
req,
)

require.Equal(t, 400, res.Status)
require.NotNil(t, res.Body)
require.NotNil(t, res.Body.Error)
require.Equal(t, "You must provide a valid root key in the Authorization header in the format 'Bearer ROOT_KEY'. Your authorization header is missing the 'Bearer ' prefix.", res.Body.Error.Detail)
})
authz.Test401[handler.Request, handler.Response](t,
func(h *testutil.Harness) zen.Route {
return &handler.Handler{
Logger: h.Logger,
DB: h.DB,
Keys: h.Keys,
Auditlogs: h.Auditlogs,
Caches: h.Caches,
}
},
func() handler.Request {
return handler.Request{
ApiId: uid.New(uid.APIPrefix),
}
},
)
}
Loading