fix(backend): enforce private-by-default visibility and pool-op ACL

Listings returned every row regardless of ownership: GET /files, /tags,
/pools and /categories exposed other users' private items (while the
single-item GET correctly returned 403), and the pool file operations
(GET /pools/:id, /pools/:id/files, add/remove/reorder) skipped ACL
entirely, so any authenticated user could read and rewrite anyone's
private pool.

- List queries now filter to rows the caller may see (public, owned, or
  granted can_view) via a shared SQL condition; admins bypass. The viewer
  identity is taken from the request context by the service and passed to
  the repository in the list params.
- Tag/Category/Pool single-item Get now enforce CanView (File already did).
- Pool Get/ListFiles require pool view; AddFiles/RemoveFiles/Reorder
  require pool edit.

Adds regression tests for private-by-default listing (hidden / public /
granted / admin) and for pool operations rejecting a non-owner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 15:07:17 +03:00
parent 2af3c481bb
commit 89ba6bae82
12 changed files with 288 additions and 15 deletions
+3 -1
View File
@@ -406,8 +406,10 @@ func (s *FileService) Replace(ctx context.Context, id uuid.UUID, p UploadParams)
return updated, nil
}
// List delegates to FileRepo with the given params.
// List delegates to FileRepo with the given params, restricting results to
// files the caller may see (unless they are an admin).
func (s *FileService) List(ctx context.Context, params domain.FileListParams) (*domain.FilePage, error) {
params.ViewerID, params.ViewerIsAdmin, _ = domain.UserFromContext(ctx)
return s.files.List(ctx, params)
}