Configure Supabase Storage bucket for report files
epic-bufdir-report-history-foundation-task-003 — Create the bufdir-reports Supabase Storage bucket with a 5-year retention policy. Configure bucket-level RLS so that only authenticated users belonging to the owning organization can read their org's files. Write access restricted to coordinator and admin roles. Set MIME type allowlist (PDF, CSV, XLSX). Configure max file size limit of 50 MB per upload. Document the bucket configuration in infrastructure notes.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Create the bucket via Supabase SQL migration or the Supabase CLI (`supabase storage create`) so configuration is version-controlled. The SQL approach uses `INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types) VALUES ('bufdir-reports', 'bufdir-reports', false, 52428800, ARRAY['application/pdf','text/csv','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'])`. Storage RLS policies in Supabase are written against the `storage.objects` table. Use `(storage.foldername(name))[1] = (auth.jwt() ->> 'organization_id')` to enforce org-scoped path prefixes.
Note that Supabase Storage retention/lifecycle policies may not be available on all tiers — if unavailable, document a manual deletion schedule and set a reminder. The 5-year retention aligns with Norwegian public record-keeping requirements for Bufdir grant documentation. Enforce organization-scoped paths in the ReportFileStorageClient (task-006) as a second layer of defense.
Testing Requirements
Write infrastructure integration tests: (1) Upload a valid PDF under org A's path as a coordinator — assert 200 response and file appears in storage; (2) Upload a .docx file — assert rejection with MIME error; (3) Upload a 51 MB file — assert rejection with size error; (4) Authenticate as org B user, attempt to download org A's file via direct path — assert 403; (5) Generate a signed URL for a file, wait for expiry, attempt to use it — assert 403 after expiry; (6) Attempt upload as peer_mentor role — assert permission denied. Use Supabase local dev environment for all tests. Document test results in the infrastructure notes file.
Incorrectly authored RLS policies could silently allow cross-organization data reads, exposing sensitive report history of one organization to coordinators of another in a multi-tenant environment.
Mitigation & Contingency
Mitigation: Write integration tests that explicitly authenticate as a user from organization A and assert zero rows are returned for organization B's history records. Use Supabase's built-in RLS testing utilities and review policies with a second developer.
Contingency: If a cross-tenant leak is discovered post-deployment, immediately revoke all active sessions for affected organizations, audit query logs for unauthorized access, and patch the RLS policy in a hotfix migration before re-enabling access.
The 5-year retention policy for report files may conflict with Supabase Storage's lack of native lifecycle rules, requiring a custom pg_cron job that could fail silently and either delete files prematurely or never clean up.
Mitigation & Contingency
Mitigation: Implement the retention cleanup as a documented pg_cron job with explicit logging to a separate audit_jobs table. Add a Supabase Edge Function health check that verifies the cron job ran within the last 25 hours.
Contingency: If the cron job fails, files accumulate in storage (non-critical for compliance — over-retention is safer than under-retention). Alert the ops team via monitoring and manually trigger the cleanup function once the cron issue is resolved.
Signed URL generation depends on the requesting user's Supabase session being valid at the time of the call. If sessions expire during a long screen interaction, URL generation will fail with an authorization error and confuse the coordinator.
Mitigation & Contingency
Mitigation: Wrap signed URL generation in the service layer with a session-refresh check before calling Supabase Storage. Generate URLs on demand (tap-to-download) rather than pre-generating them for all list items on screen load.
Contingency: If a URL generation fails due to session expiry, surface a clear error message prompting the coordinator to re-authenticate, then automatically retry URL generation after session refresh completes.