From 100a9bb282dd4226368b4e5fac398726c7acf653 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Thu, 30 Apr 2026 11:28:21 +0300 Subject: task da: enforce media access and owner/admin role permissions Changes: - MediaService.ListMedia now accepts userID and filters by allowed sets for non-admins via AllowedSetIDs in repository.MediaFilter. - Handlers pass userID into ListMedia; API returns 403 for forbidden. - Added verifyModifyAccess and verifySetModifyAccess helpers so only owners/admins can upload, soft-delete, restore, and regenerate thumbnails/covers; viewers are blocked. - GetMediaDetail, ToggleFavorite, AssignTag, RemoveTag, notes, and shares now consistently verifyAccess before proceeding. - Handlers handle ErrForbidden with 403 for soft-delete and restore. - Added negative tests proving viewers cannot mutate and unauthorized users cannot access/detail/tag/note/favorite/share inaccessible media. --- internal/api/handlers_test.go | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) (limited to 'internal/api/handlers_test.go') diff --git a/internal/api/handlers_test.go b/internal/api/handlers_test.go index c853140..798ac81 100644 --- a/internal/api/handlers_test.go +++ b/internal/api/handlers_test.go @@ -587,7 +587,7 @@ func TestServer_MediaList(t *testing.T) { t.Run(tt.name, func(t *testing.T) { var gotFilter repository.MediaFilter ms := &service.MockMediaService{ - ListMediaFunc: func(ctx context.Context, filter repository.MediaFilter) ([]model.Media, error) { + ListMediaFunc: func(ctx context.Context, userID int64, filter repository.MediaFilter) ([]model.Media, error) { gotFilter = filter return tt.listResult, tt.listErr }, @@ -782,6 +782,46 @@ func TestServer_Restore(t *testing.T) { } } +func TestServer_SoftDelete_Forbidden(t *testing.T) { + ms := &service.MockMediaService{ + SoftDeleteMediaFunc: func(ctx context.Context, mediaID, userID int64) error { + return service.ErrForbidden + }, + } + store := buildSessionStore(1) + sm := auth.NewSessionManager(store, &clock.MockClock{T: time.Now()}, time.Hour) + cfg := &internal.Config{SessionTimeoutHours: 24} + srv := newTestServer(t, buildCountStore(1), nil, sm, cfg, ms, nil, nil, nil) + + req := httptest.NewRequest(http.MethodDelete, "/api/media/99", nil) + req.AddCookie(addSessionCookie(t, store, sm, 1)) + rr := httptest.NewRecorder() + srv.ServeHTTP(rr, req) + if rr.Code != http.StatusForbidden { + t.Fatalf("expected %d, got %d", http.StatusForbidden, rr.Code) + } +} + +func TestServer_Restore_Forbidden(t *testing.T) { + ms := &service.MockMediaService{ + RestoreMediaFunc: func(ctx context.Context, mediaID, userID int64) error { + return service.ErrForbidden + }, + } + store := buildSessionStore(1) + sm := auth.NewSessionManager(store, &clock.MockClock{T: time.Now()}, time.Hour) + cfg := &internal.Config{SessionTimeoutHours: 24} + srv := newTestServer(t, buildCountStore(1), nil, sm, cfg, ms, nil, nil, nil) + + req := httptest.NewRequest(http.MethodPost, "/api/media/99/restore", nil) + req.AddCookie(addSessionCookie(t, store, sm, 1)) + rr := httptest.NewRecorder() + srv.ServeHTTP(rr, req) + if rr.Code != http.StatusForbidden { + t.Fatalf("expected %d, got %d", http.StatusForbidden, rr.Code) + } +} + // ------------------------------------------------------------------ // Notes // ------------------------------------------------------------------ -- cgit v1.2.3