# Pattern 15 — Per-user private storage bucket

## What this is

RLS policies for Supabase Storage that restrict each user to files stored under their own user-ID prefix. The foundational private storage pattern — user profile photos, personal exports, private documents.

## The foldername() function

`storage.foldername(name)` splits a file path on `/` and returns the segments as a text array:

```sql
select (storage.foldername('a1b2c3/documents/report.pdf'))[1];
-- Returns: 'a1b2c3'
```

Comparing this first segment to `auth.uid()::text` is the RLS gate. A file at `a1b2c3/report.pdf` is only accessible to the user with UID `a1b2c3`.

## Four policies are required

Supabase Storage RLS is per-operation. Missing any operation leaves it either open (all authenticated users can do it) or completely blocked (no one can). You need all four:

| Operation | Policy type | What it controls |
|---|---|---|
| SELECT | `for select` + `using` | Download / list files |
| INSERT | `for insert` + `with check` | Upload |
| UPDATE | `for update` + `using` | Metadata changes |
| DELETE | `for delete` + `using` | Remove files |

## Path convention enforcement

The INSERT policy enforces the path at the database layer. But your client code also needs to construct the path correctly:

```ts
// Correct: user ID is the first path segment
const path = `${user.id}/${fileName}`;
await supabase.storage.from('private-uploads').upload(path, file);

// Wrong: no user-ID prefix — INSERT policy will reject this
await supabase.storage.from('private-uploads').upload(fileName, file);
```

## Common mistake

Using only a bucket-level check without the folder predicate:

```sql
-- Dangerous: every authenticated user can access every file in the bucket
create policy "bad_select"
  on storage.objects for select to authenticated
  using (bucket_id = 'private-uploads');
```

The `bucket_id` check is necessary but not sufficient. The `foldername` check is what enforces per-user isolation.

## Testing

```bash
# Upload a file as user A
curl -X POST "https://<project>.supabase.co/storage/v1/object/private-uploads/<user-a-id>/test.txt" \
  -H "Authorization: Bearer <user-a-token>" \
  --data-binary "hello"

# Attempt to download as user B — should fail with 400/403
curl "https://<project>.supabase.co/storage/v1/object/private-uploads/<user-a-id>/test.txt" \
  -H "Authorization: Bearer <user-b-token>"
```
