From 4ed387aece470269d2a6949afd955918db658637 Mon Sep 17 00:00:00 2001 From: Flo <53355483+Flo4604@users.noreply.github.com> Date: Thu, 31 Jul 2025 12:37:07 +0200 Subject: [PATCH 1/6] fix api zod errors --- go/apps/api/openapi/gen.go | 54 +++++++++---------- go/apps/api/openapi/openapi-generated.yaml | 4 +- .../V2PermissionsGetRoleResponseData.yaml | 2 +- .../V2PermissionsListRolesResponseData.yaml | 2 +- .../api/routes/v2_apis_list_keys/handler.go | 25 +++++---- 5 files changed, 46 insertions(+), 41 deletions(-) diff --git a/go/apps/api/openapi/gen.go b/go/apps/api/openapi/gen.go index c0448ec41f..d3a8fe187d 100644 --- a/go/apps/api/openapi/gen.go +++ b/go/apps/api/openapi/gen.go @@ -414,6 +414,33 @@ type RatelimitResponse struct { Name string `json:"name"` } +// Role defines model for Role. +type Role struct { + // Description Optional detailed explanation of what this role encompasses and what access it provides. + // Helps team members understand the role's scope, intended use cases, and security implications. + // Include information about what types of users should receive this role and what they can accomplish. + // Not visible to end users - this is for internal documentation and access control audits. + Description *string `json:"description,omitempty"` + + // Id The unique identifier for this role within Unkey's system. + // Generated automatically when the role is created and used to reference this role in API operations. + // Always begins with 'role_' followed by alphanumeric characters and underscores. + Id string `json:"id"` + + // Name The human-readable name for this role that describes its function. + // Should be descriptive enough for administrators to understand what access this role provides. + // Use clear, semantic names that reflect the job function or responsibility level. + // Names must be unique within your workspace to avoid confusion during role assignment. + Name string `json:"name"` + + // Permissions Complete list of permissions currently assigned to this role. + // Each permission grants specific access rights that will be inherited by any keys or users assigned this role. + // Use this list to understand the full scope of access provided by this role. + // Permissions can be added or removed from roles without affecting the role's identity or other properties. + // Empty array indicates a role with no permissions currently assigned. + Permissions []Permission `json:"permissions"` +} + // UnauthorizedErrorResponse Error response when authentication has failed or credentials are missing. This occurs when: // - No authentication token is provided in the request // - The provided token is invalid, expired, or malformed @@ -1904,33 +1931,6 @@ type VerifyKeyRatelimitData struct { Reset int64 `json:"reset"` } -// Role defines model for role. -type Role struct { - // Description Optional detailed explanation of what this role encompasses and what access it provides. - // Helps team members understand the role's scope, intended use cases, and security implications. - // Include information about what types of users should receive this role and what they can accomplish. - // Not visible to end users - this is for internal documentation and access control audits. - Description *string `json:"description,omitempty"` - - // Id The unique identifier for this role within Unkey's system. - // Generated automatically when the role is created and used to reference this role in API operations. - // Always begins with 'role_' followed by alphanumeric characters and underscores. - Id string `json:"id"` - - // Name The human-readable name for this role that describes its function. - // Should be descriptive enough for administrators to understand what access this role provides. - // Use clear, semantic names that reflect the job function or responsibility level. - // Names must be unique within your workspace to avoid confusion during role assignment. - Name string `json:"name"` - - // Permissions Complete list of permissions currently assigned to this role. - // Each permission grants specific access rights that will be inherited by any keys or users assigned this role. - // Use this list to understand the full scope of access provided by this role. - // Permissions can be added or removed from roles without affecting the role's identity or other properties. - // Empty array indicates a role with no permissions currently assigned. - Permissions []Permission `json:"permissions"` -} - // ChproxyMetricsJSONRequestBody defines body for ChproxyMetrics for application/json ContentType. type ChproxyMetricsJSONRequestBody = ChproxyMetricsRequestBody diff --git a/go/apps/api/openapi/openapi-generated.yaml b/go/apps/api/openapi/openapi-generated.yaml index 36024380c7..f8883e6f98 100644 --- a/go/apps/api/openapi/openapi-generated.yaml +++ b/go/apps/api/openapi/openapi-generated.yaml @@ -2811,7 +2811,7 @@ components: type: object properties: role: - "$ref": "#/components/schemas/role" + "$ref": "#/components/schemas/Role" required: - role additionalProperties: false @@ -2827,7 +2827,7 @@ components: maxItems: 1000 description: Array of roles with their assigned permissions. items: - "$ref": "#/components/schemas/role" + "$ref": "#/components/schemas/Role" V2RatelimitDeleteOverrideResponseData: type: object additionalProperties: false diff --git a/go/apps/api/openapi/spec/paths/v2/permissions/getRole/V2PermissionsGetRoleResponseData.yaml b/go/apps/api/openapi/spec/paths/v2/permissions/getRole/V2PermissionsGetRoleResponseData.yaml index b67f1238f7..7d6c268a18 100644 --- a/go/apps/api/openapi/spec/paths/v2/permissions/getRole/V2PermissionsGetRoleResponseData.yaml +++ b/go/apps/api/openapi/spec/paths/v2/permissions/getRole/V2PermissionsGetRoleResponseData.yaml @@ -1,7 +1,7 @@ type: object properties: role: - "$ref": "../../../../common/role.yaml" + "$ref": "../../../../common/Role.yaml" required: - role additionalProperties: false diff --git a/go/apps/api/openapi/spec/paths/v2/permissions/listRoles/V2PermissionsListRolesResponseData.yaml b/go/apps/api/openapi/spec/paths/v2/permissions/listRoles/V2PermissionsListRolesResponseData.yaml index e0c4bf1f5a..31fb8044ea 100644 --- a/go/apps/api/openapi/spec/paths/v2/permissions/listRoles/V2PermissionsListRolesResponseData.yaml +++ b/go/apps/api/openapi/spec/paths/v2/permissions/listRoles/V2PermissionsListRolesResponseData.yaml @@ -2,4 +2,4 @@ type: array maxItems: 1000 description: Array of roles with their assigned permissions. items: - "$ref": "../../../../common/role.yaml" + "$ref": "../../../../common/Role.yaml" diff --git a/go/apps/api/routes/v2_apis_list_keys/handler.go b/go/apps/api/routes/v2_apis_list_keys/handler.go index 06ea93fc68..d2f42b38ee 100644 --- a/go/apps/api/routes/v2_apis_list_keys/handler.go +++ b/go/apps/api/routes/v2_apis_list_keys/handler.go @@ -327,11 +327,11 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { Identity: nil, Meta: nil, Name: nil, - Permissions: nil, Plaintext: nil, - Ratelimits: nil, - Roles: nil, UpdatedAt: nil, + Ratelimits: ptr.P([]openapi.RatelimitResponse{}), + Permissions: ptr.P([]string{}), + Roles: ptr.P([]string{}), } if key.Key.Name.Valid { @@ -388,7 +388,7 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { k.Identity = &openapi.Identity{ ExternalId: key.ExternalID.String, Meta: nil, - Ratelimits: nil, + Ratelimits: []openapi.RatelimitResponse{}, } if len(key.IdentityMeta) > 0 { @@ -423,7 +423,10 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { return fault.Wrap(err, fault.Code(codes.App.Internal.UnexpectedError.URN()), fault.Internal("unable to find permissions for key"), fault.Public("Could not load permissions for key.")) } - k.Permissions = ptr.P(permissionSlugs) + + if len(permissionSlugs) > 0 { + k.Permissions = ptr.P(permissionSlugs) + } // Get roles for the key roles, err := db.Query.ListRolesByKeyID(ctx, h.DB.RO(), k.KeyId) @@ -432,12 +435,14 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { fault.Internal("unable to find roles for key"), fault.Public("Could not load roles for key.")) } - roleNames := make([]string, len(roles)) - for i, role := range roles { - roleNames[i] = role.Name - } + if len(roles) > 0 { + roleNames := make([]string, len(roles)) + for i, role := range roles { + roleNames[i] = role.Name + } - k.Roles = ptr.P(roleNames) + k.Roles = ptr.P(roleNames) + } // Add ratelimits for the key if keyRatelimits, exists := ratelimitsMap[k.KeyId]; exists { From dbe030b11ff0cbf8a0959779c757e05f7a5b129a Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 10:39:15 +0000 Subject: [PATCH 2/6] [autofix.ci] apply automated fixes --- go/benchmarks/keyverify.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/go/benchmarks/keyverify.js b/go/benchmarks/keyverify.js index 923c9e8127..cabef62239 100644 --- a/go/benchmarks/keyverify.js +++ b/go/benchmarks/keyverify.js @@ -76,7 +76,6 @@ const headers = { Authorization: `Bearer ${UNKEY_ROOT_KEY}`, }; - export function testV1KeyVerify() { const key = keys[Math.floor(Math.random() * keys.length)]; @@ -112,7 +111,6 @@ export function testV2KeyVerify() { }, ); - check(response, { "status is 200": (r) => r.status === 200, }); From 04f7242678583cd3e280572d03d449d7ba4f9c16 Mon Sep 17 00:00:00 2001 From: Flo <53355483+Flo4604@users.noreply.github.com> Date: Thu, 31 Jul 2025 13:19:33 +0200 Subject: [PATCH 3/6] make array handling uniform --- go/apps/api/openapi/gen.go | 2 +- go/apps/api/openapi/openapi-generated.yaml | 1 - go/apps/api/openapi/spec/common/Identity.yaml | 1 - .../api/routes/v2_apis_list_keys/handler.go | 4 +- .../v2_identities_list_identities/200_test.go | 4 +- .../v2_identities_list_identities/handler.go | 6 +- .../v2_identities_update_identity/200_test.go | 22 +++---- .../v2_identities_update_identity/handler.go | 17 +++-- go/apps/api/routes/v2_keys_get_key/handler.go | 7 +- .../api/routes/v2_keys_verify_key/handler.go | 66 +++++++++++-------- go/apps/api/routes/v2_keys_whoami/handler.go | 7 +- 11 files changed, 83 insertions(+), 54 deletions(-) diff --git a/go/apps/api/openapi/gen.go b/go/apps/api/openapi/gen.go index d3a8fe187d..cda8c528d9 100644 --- a/go/apps/api/openapi/gen.go +++ b/go/apps/api/openapi/gen.go @@ -146,7 +146,7 @@ type Identity struct { // Meta Identity metadata Meta *map[string]interface{} `json:"meta,omitempty"` - Ratelimits []RatelimitResponse `json:"ratelimits"` + Ratelimits *[]RatelimitResponse `json:"ratelimits,omitempty"` } // InternalServerErrorResponse Error response when an unexpected error occurs on the server. This indicates a problem with Unkey's systems rather than your request. diff --git a/go/apps/api/openapi/openapi-generated.yaml b/go/apps/api/openapi/openapi-generated.yaml index f8883e6f98..f2a354455d 100644 --- a/go/apps/api/openapi/openapi-generated.yaml +++ b/go/apps/api/openapi/openapi-generated.yaml @@ -2180,7 +2180,6 @@ components: description: {} required: - externalId - - ratelimits RatelimitResponse: type: object properties: diff --git a/go/apps/api/openapi/spec/common/Identity.yaml b/go/apps/api/openapi/spec/common/Identity.yaml index cdf01d9d41..93c20e5dd8 100644 --- a/go/apps/api/openapi/spec/common/Identity.yaml +++ b/go/apps/api/openapi/spec/common/Identity.yaml @@ -13,4 +13,3 @@ properties: description: Identity ratelimits required: - externalId - - ratelimits diff --git a/go/apps/api/routes/v2_apis_list_keys/handler.go b/go/apps/api/routes/v2_apis_list_keys/handler.go index d2f42b38ee..db9f03b823 100644 --- a/go/apps/api/routes/v2_apis_list_keys/handler.go +++ b/go/apps/api/routes/v2_apis_list_keys/handler.go @@ -388,7 +388,7 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { k.Identity = &openapi.Identity{ ExternalId: key.ExternalID.String, Meta: nil, - Ratelimits: []openapi.RatelimitResponse{}, + Ratelimits: nil, } if len(key.IdentityMeta) > 0 { @@ -411,7 +411,7 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { } } - k.Identity.Ratelimits = ratelimitsResponse + k.Identity.Ratelimits = ptr.P(ratelimitsResponse) } } diff --git a/go/apps/api/routes/v2_identities_list_identities/200_test.go b/go/apps/api/routes/v2_identities_list_identities/200_test.go index b1f4731ed1..d7d8994188 100644 --- a/go/apps/api/routes/v2_identities_list_identities/200_test.go +++ b/go/apps/api/routes/v2_identities_list_identities/200_test.go @@ -105,9 +105,9 @@ func TestSuccess(t *testing.T) { // Check if this identity should have ratelimits if i%2 == 0 { - require.GreaterOrEqual(t, len(identity.Ratelimits), 1, "identity %s should have at least 1 ratelimit", id) + require.GreaterOrEqual(t, len(*identity.Ratelimits), 1, "identity %s should have at least 1 ratelimit", id) hasApiCallsLimit := false - for _, rl := range identity.Ratelimits { + for _, rl := range *identity.Ratelimits { if rl.Name == "api_calls" { hasApiCallsLimit = true assert.Equal(t, int64(100), rl.Limit) diff --git a/go/apps/api/routes/v2_identities_list_identities/handler.go b/go/apps/api/routes/v2_identities_list_identities/handler.go index 59ed3e0991..6421801521 100644 --- a/go/apps/api/routes/v2_identities_list_identities/handler.go +++ b/go/apps/api/routes/v2_identities_list_identities/handler.go @@ -126,10 +126,14 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { // Create a new identity with its ratelimits newIdentity := openapi.Identity{ ExternalId: identity.ExternalID, - Ratelimits: formattedRatelimits, + Ratelimits: nil, Meta: nil, } + if len(formattedRatelimits) > 0 { + newIdentity.Ratelimits = ptr.P(formattedRatelimits) + } + // Add metadata if available if len(identity.Meta) > 0 { // Initialize the Meta field with an empty map diff --git a/go/apps/api/routes/v2_identities_update_identity/200_test.go b/go/apps/api/routes/v2_identities_update_identity/200_test.go index 0ab9b7229c..8c2d619222 100644 --- a/go/apps/api/routes/v2_identities_update_identity/200_test.go +++ b/go/apps/api/routes/v2_identities_update_identity/200_test.go @@ -124,8 +124,7 @@ func TestSuccess(t *testing.T) { assert.Equal(t, true, meta["active"]) // Verify no ratelimits - require.NotNil(t, res.Body.Data.Ratelimits) - assert.Empty(t, res.Body.Data.Ratelimits) + require.Nil(t, res.Body.Data.Ratelimits) }) t.Run("update ratelimits - add new, update existing, delete one", func(t *testing.T) { @@ -161,16 +160,16 @@ func TestSuccess(t *testing.T) { // Verify exactly 2 ratelimits (should have removed 'special_feature') require.NotNil(t, res.Body.Data.Ratelimits) - require.Len(t, res.Body.Data.Ratelimits, 2) + require.Len(t, *res.Body.Data.Ratelimits, 2) // Check ratelimit values var apiCallsLimit, newFeatureLimit *openapi.RatelimitResponse - for i := range res.Body.Data.Ratelimits { - switch (res.Body.Data.Ratelimits)[i].Name { + for i := range *res.Body.Data.Ratelimits { + switch (*res.Body.Data.Ratelimits)[i].Name { case "api_calls": - apiCallsLimit = &(res.Body.Data.Ratelimits)[i] + apiCallsLimit = &(*res.Body.Data.Ratelimits)[i] case "new_feature": - newFeatureLimit = &(res.Body.Data.Ratelimits)[i] + newFeatureLimit = &(*res.Body.Data.Ratelimits)[i] } } @@ -186,7 +185,7 @@ func TestSuccess(t *testing.T) { assert.Equal(t, int64(86400000), newFeatureLimit.Duration) // Verify 'special_feature' was removed - for _, rl := range res.Body.Data.Ratelimits { + for _, rl := range *res.Body.Data.Ratelimits { assert.NotEqual(t, "special_feature", rl.Name, "special_feature should have been removed") } }) @@ -207,8 +206,7 @@ func TestSuccess(t *testing.T) { require.Equal(t, externalID, res.Body.Data.ExternalId) // Verify no ratelimits - require.NotNil(t, res.Body.Data.Ratelimits) - assert.Empty(t, res.Body.Data.Ratelimits) + require.Nil(t, res.Body.Data.Ratelimits) }) t.Run("clear metadata", func(t *testing.T) { @@ -263,8 +261,8 @@ func TestSuccess(t *testing.T) { // Verify ratelimits require.NotNil(t, res.Body.Data.Ratelimits) - require.Len(t, res.Body.Data.Ratelimits, 1) - rlimits := res.Body.Data.Ratelimits + require.Len(t, *res.Body.Data.Ratelimits, 1) + rlimits := *res.Body.Data.Ratelimits assert.Equal(t, "enterprise_feature", rlimits[0].Name) assert.Equal(t, int64(50), rlimits[0].Limit) assert.Equal(t, int64(3600000), rlimits[0].Duration) diff --git a/go/apps/api/routes/v2_identities_update_identity/handler.go b/go/apps/api/routes/v2_identities_update_identity/handler.go index 21b855dc26..4ac49eddcf 100644 --- a/go/apps/api/routes/v2_identities_update_identity/handler.go +++ b/go/apps/api/routes/v2_identities_update_identity/handler.go @@ -16,6 +16,7 @@ import ( "github.com/unkeyed/unkey/go/pkg/db" "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/otel/logging" + "github.com/unkeyed/unkey/go/pkg/ptr" "github.com/unkeyed/unkey/go/pkg/rbac" "github.com/unkeyed/unkey/go/pkg/uid" "github.com/unkeyed/unkey/go/pkg/zen" @@ -391,15 +392,21 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { }) } + identityData := openapi.Identity{ + ExternalId: req.ExternalId, + Meta: req.Meta, + Ratelimits: nil, + } + + if len(responseRatelimits) > 0 { + identityData.Ratelimits = ptr.P(responseRatelimits) + } + response := Response{ Meta: openapi.Meta{ RequestId: s.RequestID(), }, - Data: openapi.Identity{ - ExternalId: req.ExternalId, - Meta: req.Meta, - Ratelimits: responseRatelimits, - }, + Data: identityData, } return s.JSON(http.StatusOK, response) diff --git a/go/apps/api/routes/v2_keys_get_key/handler.go b/go/apps/api/routes/v2_keys_get_key/handler.go index 703527c212..0710b3f3f0 100644 --- a/go/apps/api/routes/v2_keys_get_key/handler.go +++ b/go/apps/api/routes/v2_keys_get_key/handler.go @@ -276,8 +276,9 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { ) } + identityRatelimits := make([]openapi.RatelimitResponse, 0) for _, ratelimit := range ratelimits { - k.Identity.Ratelimits = append(k.Identity.Ratelimits, openapi.RatelimitResponse{ + identityRatelimits = append(identityRatelimits, openapi.RatelimitResponse{ Id: ratelimit.ID, Duration: ratelimit.Duration, Limit: int64(ratelimit.Limit), @@ -285,6 +286,10 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { AutoApply: ratelimit.AutoApply, }) } + + if len(identityRatelimits) > 0 { + k.Identity.Ratelimits = ptr.P(identityRatelimits) + } } ratelimits, err := db.Query.ListRatelimitsByKeyID(ctx, h.DB.RO(), sql.NullString{String: key.ID, Valid: true}) diff --git a/go/apps/api/routes/v2_keys_verify_key/handler.go b/go/apps/api/routes/v2_keys_verify_key/handler.go index a8ebe4bcf0..729895bcc5 100644 --- a/go/apps/api/routes/v2_keys_verify_key/handler.go +++ b/go/apps/api/routes/v2_keys_verify_key/handler.go @@ -159,38 +159,40 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { return err } - res := Response{ - Meta: openapi.Meta{ - RequestId: s.RequestID(), - }, - // nolint:exhaustruct - Data: openapi.V2KeysVerifyKeyResponseData{ - Code: key.ToOpenAPIStatus(), - Valid: key.Status == keys.StatusValid, - Enabled: ptr.P(key.Key.Enabled), - Name: ptr.P(key.Key.Name.String), - Permissions: ptr.P(key.Permissions), - Roles: ptr.P(key.Roles), - KeyId: ptr.P(key.Key.ID), - Credits: nil, - Expires: nil, - Identity: nil, - Meta: nil, - Ratelimits: nil, - }, + keyData := openapi.V2KeysVerifyKeyResponseData{ + Code: key.ToOpenAPIStatus(), + Valid: key.Status == keys.StatusValid, + Enabled: ptr.P(key.Key.Enabled), + Name: ptr.P(key.Key.Name.String), + KeyId: ptr.P(key.Key.ID), + Permissions: nil, + Roles: nil, + Credits: nil, + Expires: nil, + Identity: nil, + Meta: nil, + Ratelimits: nil, + } + + if len(key.Permissions) > 0 { + keyData.Permissions = ptr.P(key.Permissions) + } + + if len(key.Roles) > 0 { + keyData.Roles = ptr.P(key.Roles) } remaining := key.Key.RemainingRequests if remaining.Valid { - res.Data.Credits = ptr.P(remaining.Int32) + keyData.Credits = ptr.P(remaining.Int32) } if key.Key.Expires.Valid { - res.Data.Expires = ptr.P(key.Key.Expires.Time.UnixMilli()) + keyData.Expires = ptr.P(key.Key.Expires.Time.UnixMilli()) } if key.Key.Meta.Valid { - err = json.Unmarshal([]byte(key.Key.Meta.String), &res.Data.Meta) + err = json.Unmarshal([]byte(key.Key.Meta.String), &keyData.Meta) if err != nil { return fault.Wrap(err, fault.Code(codes.App.Internal.UnexpectedError.URN()), fault.Internal("unable to unmarshal key meta"), @@ -200,18 +202,19 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { } if key.Key.IdentityID.Valid { - res.Data.Identity = &openapi.Identity{ + keyData.Identity = &openapi.Identity{ ExternalId: key.Key.ExternalID.String, Ratelimits: nil, Meta: nil, } + identityRatelimits := make([]openapi.RatelimitResponse, 0) for _, ratelimit := range key.GetRatelimitConfigs() { if ratelimit.IdentityID == "" { continue } - res.Data.Identity.Ratelimits = append(res.Data.Identity.Ratelimits, openapi.RatelimitResponse{ + identityRatelimits = append(identityRatelimits, openapi.RatelimitResponse{ AutoApply: ratelimit.AutoApply == 1, Duration: int64(ratelimit.Duration), Id: ratelimit.ID, @@ -220,8 +223,12 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { }) } + if len(identityRatelimits) > 0 { + keyData.Identity.Ratelimits = ptr.P(identityRatelimits) + } + if len(key.Key.IdentityMeta) > 0 { - err = json.Unmarshal(key.Key.IdentityMeta, &res.Data.Identity.Meta) + err = json.Unmarshal(key.Key.IdentityMeta, &keyData.Identity.Meta) if err != nil { return fault.Wrap(err, fault.Code(codes.App.Internal.UnexpectedError.URN()), fault.Internal("unable to unmarshal identity meta"), @@ -251,9 +258,14 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { } if len(ratelimitResponse) > 0 { - res.Data.Ratelimits = ptr.P(ratelimitResponse) + keyData.Ratelimits = ptr.P(ratelimitResponse) } } - return s.JSON(http.StatusOK, res) + return s.JSON(http.StatusOK, Response{ + Meta: openapi.Meta{ + RequestId: s.RequestID(), + }, + Data: keyData, + }) } diff --git a/go/apps/api/routes/v2_keys_whoami/handler.go b/go/apps/api/routes/v2_keys_whoami/handler.go index cc6b7eee8b..8a8762d40b 100644 --- a/go/apps/api/routes/v2_keys_whoami/handler.go +++ b/go/apps/api/routes/v2_keys_whoami/handler.go @@ -204,8 +204,9 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { ) } + identityRatelimits := make([]openapi.RatelimitResponse, len(ratelimits)) for _, ratelimit := range ratelimits { - k.Identity.Ratelimits = append(k.Identity.Ratelimits, openapi.RatelimitResponse{ + identityRatelimits = append(identityRatelimits, openapi.RatelimitResponse{ Id: ratelimit.ID, Duration: ratelimit.Duration, Limit: int64(ratelimit.Limit), @@ -213,6 +214,10 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { AutoApply: ratelimit.AutoApply, }) } + + if len(identityRatelimits) > 0 { + k.Identity.Ratelimits = ptr.P(identityRatelimits) + } } ratelimits, err := db.Query.ListRatelimitsByKeyID(ctx, h.DB.RO(), sql.NullString{String: key.ID, Valid: true}) From 20d2dc57cdc48c423de207224d77633a371afcd5 Mon Sep 17 00:00:00 2001 From: Flo <53355483+Flo4604@users.noreply.github.com> Date: Thu, 31 Jul 2025 13:27:56 +0200 Subject: [PATCH 4/6] make array handling uniform --- go/apps/api/routes/v2_apis_list_keys/handler.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/go/apps/api/routes/v2_apis_list_keys/handler.go b/go/apps/api/routes/v2_apis_list_keys/handler.go index db9f03b823..57347e1078 100644 --- a/go/apps/api/routes/v2_apis_list_keys/handler.go +++ b/go/apps/api/routes/v2_apis_list_keys/handler.go @@ -329,9 +329,9 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { Name: nil, Plaintext: nil, UpdatedAt: nil, - Ratelimits: ptr.P([]openapi.RatelimitResponse{}), - Permissions: ptr.P([]string{}), - Roles: ptr.P([]string{}), + Ratelimits: nil, + Permissions: nil, + Roles: nil, } if key.Key.Name.Valid { From 4bcd0d0e9e09d6108b7bb016f2c4573fc3ccb299 Mon Sep 17 00:00:00 2001 From: Flo <53355483+Flo4604@users.noreply.github.com> Date: Thu, 31 Jul 2025 13:33:30 +0200 Subject: [PATCH 5/6] fix rabbi comment --- go/apps/api/routes/v2_keys_whoami/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/apps/api/routes/v2_keys_whoami/handler.go b/go/apps/api/routes/v2_keys_whoami/handler.go index 8a8762d40b..6f7f94c610 100644 --- a/go/apps/api/routes/v2_keys_whoami/handler.go +++ b/go/apps/api/routes/v2_keys_whoami/handler.go @@ -204,7 +204,7 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { ) } - identityRatelimits := make([]openapi.RatelimitResponse, len(ratelimits)) + identityRatelimits := make([]openapi.RatelimitResponse, 0, len(ratelimits)) for _, ratelimit := range ratelimits { identityRatelimits = append(identityRatelimits, openapi.RatelimitResponse{ Id: ratelimit.ID, From 4b89eba445e53a399e39872fa1381faf0d6c87dc Mon Sep 17 00:00:00 2001 From: Flo <53355483+Flo4604@users.noreply.github.com> Date: Thu, 31 Jul 2025 14:13:48 +0200 Subject: [PATCH 6/6] fix: permission array for roles --- go/apps/api/openapi/gen.go | 2 +- go/apps/api/openapi/openapi-generated.yaml | 1 - go/apps/api/openapi/spec/common/Role.yaml | 1 - .../routes/v2_identities_list_identities/200_test.go | 3 +-- go/apps/api/routes/v2_keys_add_roles/handler.go | 10 +++++++++- go/apps/api/routes/v2_keys_remove_roles/handler.go | 8 +++++++- go/apps/api/routes/v2_keys_set_roles/handler.go | 8 +++++++- go/apps/api/routes/v2_permissions_get_role/200_test.go | 7 +++---- go/apps/api/routes/v2_permissions_get_role/handler.go | 9 ++++++++- .../api/routes/v2_permissions_list_roles/200_test.go | 4 ++-- .../api/routes/v2_permissions_list_roles/handler.go | 8 +++++++- 11 files changed, 45 insertions(+), 16 deletions(-) diff --git a/go/apps/api/openapi/gen.go b/go/apps/api/openapi/gen.go index cda8c528d9..5d3b38fa52 100644 --- a/go/apps/api/openapi/gen.go +++ b/go/apps/api/openapi/gen.go @@ -438,7 +438,7 @@ type Role struct { // Use this list to understand the full scope of access provided by this role. // Permissions can be added or removed from roles without affecting the role's identity or other properties. // Empty array indicates a role with no permissions currently assigned. - Permissions []Permission `json:"permissions"` + Permissions *[]Permission `json:"permissions,omitempty"` } // UnauthorizedErrorResponse Error response when authentication has failed or credentials are missing. This occurs when: diff --git a/go/apps/api/openapi/openapi-generated.yaml b/go/apps/api/openapi/openapi-generated.yaml index f2a354455d..f385d1713c 100644 --- a/go/apps/api/openapi/openapi-generated.yaml +++ b/go/apps/api/openapi/openapi-generated.yaml @@ -2459,7 +2459,6 @@ components: required: - id - name - - permissions additionalProperties: false V2KeysCreateKeyResponseData: type: object diff --git a/go/apps/api/openapi/spec/common/Role.yaml b/go/apps/api/openapi/spec/common/Role.yaml index b096c075ae..8d2f1656ca 100644 --- a/go/apps/api/openapi/spec/common/Role.yaml +++ b/go/apps/api/openapi/spec/common/Role.yaml @@ -37,5 +37,4 @@ properties: required: - id - name - - permissions additionalProperties: false diff --git a/go/apps/api/routes/v2_identities_list_identities/200_test.go b/go/apps/api/routes/v2_identities_list_identities/200_test.go index d7d8994188..33b22cd37b 100644 --- a/go/apps/api/routes/v2_identities_list_identities/200_test.go +++ b/go/apps/api/routes/v2_identities_list_identities/200_test.go @@ -360,8 +360,7 @@ func TestSuccess(t *testing.T) { require.NotNil(t, *identity.Meta, "Meta should be a valid map") } - // Ratelimits should always be present - require.NotNil(t, identity.Ratelimits, "Ratelimits should be a valid array") + require.NotNil(t, identity.Ratelimits, "Ratelimits should be set") } }) } diff --git a/go/apps/api/routes/v2_keys_add_roles/handler.go b/go/apps/api/routes/v2_keys_add_roles/handler.go index 3a597cd8bb..3c8d7b3fc4 100644 --- a/go/apps/api/routes/v2_keys_add_roles/handler.go +++ b/go/apps/api/routes/v2_keys_add_roles/handler.go @@ -17,6 +17,7 @@ import ( "github.com/unkeyed/unkey/go/pkg/db" "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/otel/logging" + "github.com/unkeyed/unkey/go/pkg/ptr" "github.com/unkeyed/unkey/go/pkg/rbac" "github.com/unkeyed/unkey/go/pkg/zen" ) @@ -246,6 +247,7 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { Id: role.ID, Name: role.Name, Description: nil, + Permissions: nil, } if role.Description.Valid { @@ -254,6 +256,8 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { rolePermissions := make([]db.Permission, 0) json.Unmarshal(role.Permissions.([]byte), &rolePermissions) + + perms := make([]openapi.Permission, 0) for _, permission := range rolePermissions { perm := openapi.Permission{ Id: permission.ID, @@ -266,7 +270,11 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { perm.Description = &permission.Description.String } - r.Permissions = append(r.Permissions, perm) + perms = append(perms, perm) + } + + if len(perms) > 0 { + r.Permissions = ptr.P(perms) } responseData = append(responseData, r) diff --git a/go/apps/api/routes/v2_keys_remove_roles/handler.go b/go/apps/api/routes/v2_keys_remove_roles/handler.go index 4efe4ca56d..a3ebc78497 100644 --- a/go/apps/api/routes/v2_keys_remove_roles/handler.go +++ b/go/apps/api/routes/v2_keys_remove_roles/handler.go @@ -16,6 +16,7 @@ import ( "github.com/unkeyed/unkey/go/pkg/db" "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/otel/logging" + "github.com/unkeyed/unkey/go/pkg/ptr" "github.com/unkeyed/unkey/go/pkg/rbac" "github.com/unkeyed/unkey/go/pkg/zen" ) @@ -226,6 +227,7 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { rolePermissions := make([]db.Permission, 0) json.Unmarshal(role.Permissions.([]byte), &rolePermissions) + perms := make([]openapi.Permission, 0) for _, permission := range rolePermissions { perm := openapi.Permission{ Id: permission.ID, @@ -238,7 +240,11 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { perm.Description = &permission.Description.String } - r.Permissions = append(r.Permissions, perm) + perms = append(perms, perm) + } + + if len(perms) > 0 { + r.Permissions = ptr.P(perms) } responseData = append(responseData, r) diff --git a/go/apps/api/routes/v2_keys_set_roles/handler.go b/go/apps/api/routes/v2_keys_set_roles/handler.go index 07267c7a62..e569333004 100644 --- a/go/apps/api/routes/v2_keys_set_roles/handler.go +++ b/go/apps/api/routes/v2_keys_set_roles/handler.go @@ -17,6 +17,7 @@ import ( "github.com/unkeyed/unkey/go/pkg/db" "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/otel/logging" + "github.com/unkeyed/unkey/go/pkg/ptr" "github.com/unkeyed/unkey/go/pkg/rbac" "github.com/unkeyed/unkey/go/pkg/zen" ) @@ -288,6 +289,7 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { rolePermissions := make([]db.Permission, 0) json.Unmarshal(role.Permissions.([]byte), &rolePermissions) + perms := make([]openapi.Permission, 0) for _, permission := range rolePermissions { perm := openapi.Permission{ Id: permission.ID, @@ -300,7 +302,11 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { perm.Description = &permission.Description.String } - r.Permissions = append(r.Permissions, perm) + perms = append(perms, perm) + } + + if len(perms) > 0 { + r.Permissions = ptr.P(perms) } responseData = append(responseData, r) diff --git a/go/apps/api/routes/v2_permissions_get_role/200_test.go b/go/apps/api/routes/v2_permissions_get_role/200_test.go index e552594283..7c697310b7 100644 --- a/go/apps/api/routes/v2_permissions_get_role/200_test.go +++ b/go/apps/api/routes/v2_permissions_get_role/200_test.go @@ -106,11 +106,11 @@ func TestSuccess(t *testing.T) { // Verify permissions require.NotNil(t, role.Permissions) - require.Len(t, role.Permissions, 2) + require.Len(t, *role.Permissions, 2) // Create a map of permission IDs for easier checking permMap := make(map[string]bool) - for _, perm := range role.Permissions { + for _, perm := range *role.Permissions { permMap[perm.Id] = true } @@ -160,7 +160,6 @@ func TestSuccess(t *testing.T) { require.Equal(t, roleDesc, *role.Description) // Verify permissions array is empty - require.Empty(t, role.Permissions) - require.Len(t, role.Permissions, 0) + require.Nil(t, role.Permissions) }) } diff --git a/go/apps/api/routes/v2_permissions_get_role/handler.go b/go/apps/api/routes/v2_permissions_get_role/handler.go index b02300fbaf..92912635c7 100644 --- a/go/apps/api/routes/v2_permissions_get_role/handler.go +++ b/go/apps/api/routes/v2_permissions_get_role/handler.go @@ -11,6 +11,7 @@ import ( "github.com/unkeyed/unkey/go/pkg/db" "github.com/unkeyed/unkey/go/pkg/fault" "github.com/unkeyed/unkey/go/pkg/otel/logging" + "github.com/unkeyed/unkey/go/pkg/ptr" "github.com/unkeyed/unkey/go/pkg/rbac" "github.com/unkeyed/unkey/go/pkg/zen" ) @@ -94,6 +95,8 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { rolePermissions := make([]db.Permission, 0) json.Unmarshal(role.Permissions.([]byte), &rolePermissions) + + perms := make([]openapi.Permission, 0) for _, perm := range rolePermissions { permission := openapi.Permission{ Id: perm.ID, @@ -107,7 +110,11 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { permission.Description = &perm.Description.String } - roleResponse.Permissions = append(roleResponse.Permissions, permission) + perms = append(perms, permission) + } + + if len(perms) > 0 { + roleResponse.Permissions = ptr.P(perms) } return s.JSON(http.StatusOK, Response{ diff --git a/go/apps/api/routes/v2_permissions_list_roles/200_test.go b/go/apps/api/routes/v2_permissions_list_roles/200_test.go index b9571ba0f6..e9731e430e 100644 --- a/go/apps/api/routes/v2_permissions_list_roles/200_test.go +++ b/go/apps/api/routes/v2_permissions_list_roles/200_test.go @@ -133,9 +133,9 @@ func TestSuccess(t *testing.T) { // Verify permissions are attached require.NotNil(t, role.Permissions) - require.Len(t, role.Permissions, 2) + require.Len(t, *role.Permissions, 2) - for _, perm := range role.Permissions { + for _, perm := range *role.Permissions { require.NotEmpty(t, perm.Id) require.NotEmpty(t, perm.Name) require.NotNil(t, perm.Description) diff --git a/go/apps/api/routes/v2_permissions_list_roles/handler.go b/go/apps/api/routes/v2_permissions_list_roles/handler.go index 9779151f2e..6dd13c4df2 100644 --- a/go/apps/api/routes/v2_permissions_list_roles/handler.go +++ b/go/apps/api/routes/v2_permissions_list_roles/handler.go @@ -105,6 +105,8 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { rolePermissions := make([]db.Permission, 0) json.Unmarshal(role.Permissions.([]byte), &rolePermissions) + perms := make([]openapi.Permission, 0) + for _, perm := range rolePermissions { permission := openapi.Permission{ Id: perm.ID, @@ -117,7 +119,11 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { permission.Description = &perm.Description.String } - roleResponse.Permissions = append(roleResponse.Permissions, permission) + perms = append(perms, permission) + } + + if len(perms) > 0 { + roleResponse.Permissions = ptr.P(perms) } roleResponses = append(roleResponses, roleResponse)