|
| 1 | +package handler_test |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "net/http" |
| 6 | + "testing" |
| 7 | + |
| 8 | + "github.com/stretchr/testify/require" |
| 9 | + "github.com/unkeyed/unkey/go/apps/api/openapi" |
| 10 | + handler "github.com/unkeyed/unkey/go/apps/api/routes/v2_keys_verify_key" |
| 11 | + "github.com/unkeyed/unkey/go/pkg/testutil" |
| 12 | + "github.com/unkeyed/unkey/go/pkg/testutil/seed" |
| 13 | +) |
| 14 | + |
| 15 | +func TestForbidden_NoVerifyPermissions(t *testing.T) { |
| 16 | + h := testutil.NewHarness(t) |
| 17 | + |
| 18 | + route := &handler.Handler{ |
| 19 | + DB: h.DB, |
| 20 | + Keys: h.Keys, |
| 21 | + Logger: h.Logger, |
| 22 | + Auditlogs: h.Auditlogs, |
| 23 | + ClickHouse: h.ClickHouse, |
| 24 | + } |
| 25 | + |
| 26 | + h.Register(route) |
| 27 | + |
| 28 | + workspace := h.Resources().UserWorkspace |
| 29 | + api := h.CreateApi(seed.CreateApiRequest{WorkspaceID: workspace.ID}) |
| 30 | + key := h.CreateKey(seed.CreateKeyRequest{ |
| 31 | + WorkspaceID: workspace.ID, |
| 32 | + KeySpaceID: api.KeyAuthID.String, |
| 33 | + }) |
| 34 | + |
| 35 | + t.Run("root key with no verify permissions returns 403", func(t *testing.T) { |
| 36 | + // Create root key with a permission that is NOT verify_key |
| 37 | + rootKeyWithoutVerify := h.CreateRootKey(workspace.ID, "api.*.read_key") |
| 38 | + |
| 39 | + req := handler.Request{ |
| 40 | + Key: key.Key, |
| 41 | + } |
| 42 | + |
| 43 | + headers := http.Header{ |
| 44 | + "Content-Type": {"application/json"}, |
| 45 | + "Authorization": {fmt.Sprintf("Bearer %s", rootKeyWithoutVerify)}, |
| 46 | + } |
| 47 | + |
| 48 | + res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, headers, req) |
| 49 | + require.Equal(t, 403, res.Status, "expected 403, received: %d", res.Status) |
| 50 | + require.NotNil(t, res.Body) |
| 51 | + require.NotNil(t, res.Body.Error) |
| 52 | + require.NotEmpty(t, res.Body.Meta.RequestId, "RequestId should be returned in error response") |
| 53 | + |
| 54 | + // Verify the error message mentions both permission options |
| 55 | + require.Contains(t, res.Body.Error.Detail, "api.*.verify_key", "error should mention wildcard permission option") |
| 56 | + require.Contains(t, res.Body.Error.Detail, "api.<API_ID>.verify_key", "error should mention specific API permission option") |
| 57 | + }) |
| 58 | + |
| 59 | + t.Run("root key with verify permission for different api returns 200 NOT_FOUND (not 403)", func(t *testing.T) { |
| 60 | + // Create a second API |
| 61 | + api2 := h.CreateApi(seed.CreateApiRequest{WorkspaceID: workspace.ID}) |
| 62 | + |
| 63 | + // Create root key with verify permission for api2 only |
| 64 | + rootKeyForApi2 := h.CreateRootKey(workspace.ID, fmt.Sprintf("api.%s.verify_key", api2.ID)) |
| 65 | + |
| 66 | + // Try to verify a key from api1 |
| 67 | + req := handler.Request{ |
| 68 | + Key: key.Key, |
| 69 | + } |
| 70 | + |
| 71 | + headers := http.Header{ |
| 72 | + "Content-Type": {"application/json"}, |
| 73 | + "Authorization": {fmt.Sprintf("Bearer %s", rootKeyForApi2)}, |
| 74 | + } |
| 75 | + |
| 76 | + // Should return 200 with NOT_FOUND (not 403) to avoid leaking key existence |
| 77 | + res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) |
| 78 | + require.Equal(t, 200, res.Status, "expected 200, received: %d", res.Status) |
| 79 | + require.NotNil(t, res.Body) |
| 80 | + require.Equal(t, openapi.NOTFOUND, res.Body.Data.Code, "should return NOT_FOUND to avoid leaking key existence") |
| 81 | + require.False(t, res.Body.Data.Valid) |
| 82 | + }) |
| 83 | + |
| 84 | + t.Run("root key with wildcard verify permission returns 200 VALID", func(t *testing.T) { |
| 85 | + // Create root key with wildcard verify permission |
| 86 | + rootKeyWithVerify := h.CreateRootKey(workspace.ID, "api.*.verify_key") |
| 87 | + |
| 88 | + req := handler.Request{ |
| 89 | + Key: key.Key, |
| 90 | + } |
| 91 | + |
| 92 | + headers := http.Header{ |
| 93 | + "Content-Type": {"application/json"}, |
| 94 | + "Authorization": {fmt.Sprintf("Bearer %s", rootKeyWithVerify)}, |
| 95 | + } |
| 96 | + |
| 97 | + res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) |
| 98 | + require.Equal(t, 200, res.Status, "expected 200, received: %d", res.Status) |
| 99 | + require.NotNil(t, res.Body) |
| 100 | + require.Equal(t, openapi.VALID, res.Body.Data.Code) |
| 101 | + require.True(t, res.Body.Data.Valid) |
| 102 | + }) |
| 103 | + |
| 104 | + t.Run("root key with specific api verify permission returns 200 VALID", func(t *testing.T) { |
| 105 | + // Create root key with specific API verify permission |
| 106 | + rootKeyWithSpecificVerify := h.CreateRootKey(workspace.ID, fmt.Sprintf("api.%s.verify_key", api.ID)) |
| 107 | + |
| 108 | + req := handler.Request{ |
| 109 | + Key: key.Key, |
| 110 | + } |
| 111 | + |
| 112 | + headers := http.Header{ |
| 113 | + "Content-Type": {"application/json"}, |
| 114 | + "Authorization": {fmt.Sprintf("Bearer %s", rootKeyWithSpecificVerify)}, |
| 115 | + } |
| 116 | + |
| 117 | + res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) |
| 118 | + require.Equal(t, 200, res.Status, "expected 200, received: %d", res.Status) |
| 119 | + require.NotNil(t, res.Body) |
| 120 | + require.Equal(t, openapi.VALID, res.Body.Data.Code) |
| 121 | + require.True(t, res.Body.Data.Valid) |
| 122 | + }) |
| 123 | +} |
0 commit comments