From 75fe4ae51946d78dd326316a15ed7f988119716c Mon Sep 17 00:00:00 2001 From: Elias Schneider Date: Tue, 20 Oct 2026 02:59:20 +0200 Subject: [PATCH] fix(backend): return API permissions sorted by key --- backend/internal/api/service.go | 12 ++++++++---- backend/internal/api/service_test.go | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/backend/internal/api/service.go b/backend/internal/api/service.go index 1e01e224..9e199159 100644 --- a/backend/internal/api/service.go +++ b/backend/internal/api/service.go @@ -46,6 +46,10 @@ func newService(db *gorm.DB, issuer string) *Service { return &Service{db: db, issuer: issuer} } +func permissionsByKey(db *gorm.DB) *gorm.DB { + return db.Order("api_permissions.key") +} + // isIssuerAudience reports whether the audience refers to Pocket ID itself (the issuer) // A custom API must not claim the issuer as its audience, otherwise its tokens would be indistinguishable from Pocket ID's own identity tokens func isIssuerAudience(audience, issuer string) bool { @@ -55,7 +59,7 @@ func isIssuerAudience(audience, issuer string) bool { func (s *Service) List(ctx context.Context, search string, listRequestOptions utils.ListRequestOptions) (apis []API, response utils.PaginationResponse, err error) { query := s.db. WithContext(ctx). - Preload("Permissions"). + Preload("Permissions", permissionsByKey). Model(&API{}) if listRequestOptions.Sort.Column == "resource" { @@ -79,7 +83,7 @@ func (s *Service) Get(ctx context.Context, tx *gorm.DB, id string) (api API, err } err = query. - Preload("Permissions"). + Preload("Permissions", permissionsByKey). Where("id = ?", id). First(&api). Error @@ -432,7 +436,7 @@ func (s *Service) ListClientAPIs(ctx context.Context, clientID string) ([]Client // Load the API rows for the grants we collected, so the caller can see the API's name and audience var apis []API err = tx.WithContext(ctx). - Preload("Permissions"). + Preload("Permissions", permissionsByKey). Where("id IN ?", slices.Collect(maps.Keys(grants))). Order("name"). Find(&apis). @@ -670,7 +674,7 @@ func (s *Service) ListAssignableAPIs(ctx context.Context, clientID, search strin query := s.db. WithContext(ctx). - Preload("Permissions"). + Preload("Permissions", permissionsByKey). Model(&API{}). Where("id NOT IN (?)", granted) diff --git a/backend/internal/api/service_test.go b/backend/internal/api/service_test.go index 8fb26e56..9e8d2a8c 100644 --- a/backend/internal/api/service_test.go +++ b/backend/internal/api/service_test.go @@ -327,6 +327,29 @@ func TestUpdatePermissionsRejectsInvalidKeyCharacters(t *testing.T) { require.NoError(t, err) } +func TestPermissionsAreReturnedByKey(t *testing.T) { + db := testutils.NewDatabaseForTest(t) + svc := New(Dependencies{DB: db}).service + + orders, err := svc.Create(t.Context(), apiCreateDto{Name: "Orders", Resource: "https://api.orders.example.com"}) + require.NoError(t, err) + + var input []apiPermissionInputDto + for _, key := range []string{"epsilon", "alpha", "delta", "beta", "gamma"} { + input = append(input, apiPermissionInputDto{Key: key, Name: key}) + } + _, err = svc.UpdatePermissions(t.Context(), orders.ID, apiPermissionsUpdateDto{Permissions: input}) + require.NoError(t, err) + got, err := svc.Get(t.Context(), nil, orders.ID) + require.NoError(t, err) + + keys := make([]string, len(got.Permissions)) + for i, p := range got.Permissions { + keys[i] = p.Key + } + assert.Equal(t, []string{"alpha", "beta", "delta", "epsilon", "gamma"}, keys) +} + func TestCreateRejectsIssuerResource(t *testing.T) { db := testutils.NewDatabaseForTest(t) const issuer = "https://id.example.com"