# File Sharing — How It Works ## Overview File sharing proxies all file operations through a Vercel serverless function (`/api/filebrowser`) to a self-hosted **FileBrowser Quantum** instance. The frontend never speaks directly to FBQ. Auth is validated server-side via Supabase on every request. --- ## Infrastructure | Component | Location | |---|---| | FBQ instance | `https://fourgebranding.krao.us` (internal: `192.168.2.200:8082`) | | API proxy | `api/filebrowser.js` (Vercel function) | | Frontend | `src/pages/FileSharing.jsx` | --- ## Auth Flow 1. Frontend gets the current Supabase session access token (`supabase.auth.getSession()`). 2. Every request to `/api/filebrowser` sends the token as both `Authorization: Bearer ` header and `sb_access_token` query param. 3. The API function validates the token against Supabase and loads the caller's `profiles` row. 4. If valid, the function uses **its own server-side FBQ token** (`FILEBROWSER_TOKEN` env var) to talk to FBQ — the frontend never sees the FBQ token. 5. The FBQ token is a long-lived API token. If it expires or returns 401, the function falls back to admin username/password login (`FILEBROWSER_ADMIN_USER` / `FILEBROWSER_ADMIN_PASS`) to get a fresh token, cached in-memory for 30 minutes. **No JWT auth from the frontend to FBQ.** That feature was removed — portal users authenticate via Supabase only. --- ## Role-Based Access Control The API function maps each portal role to a different FBQ root path: | Portal role | FBQ root | Notes | |---|---|---| | `team` | `FILEBROWSER_TEAM_ROOT` (default `/fourgebranding`) | Full access, sees everything under team root | | `client` | `FILEBROWSER_CLIENT_ROOT/{company-name}` (default `/fourgebranding/Clients/{name}`) | Scoped to their company folder; multi-company clients get a virtual root listing their companies | | `external` | `FILEBROWSER_SUBS_ROOT/{their-name}` + assigned project folders | Personal folder + read access to `Projects/{project}` folders they are members of via `project_members` table | Virtual directories (e.g. the multi-company root for clients, the `Projects/` node for externals) are synthesised by the API and never hit FBQ. Path traversal (`..`) is blocked at `normalizePath()` — throws before any FBQ call. --- ## API Actions (`/api/filebrowser?action=...`) | Action | Method | What it does | |---|---|---| | `list` | GET | List folder contents; returns `{ entries, path, canGoUp, ... }` | | `download` | GET | Returns a signed FBQ download URL + token for direct browser download | | `download-blob` | GET | Proxies the file bytes through Vercel (used for ZIP building) | | `upload-token` | POST | Returns FBQ token + URL for direct upload from browser to FBQ | | `mkdir` | POST | Creates a new folder | | `delete` | DELETE | Deletes one file/folder | | `rename` | POST | Renames a file/folder in place | | `move` | POST | Moves or copies items; `mode: 'copy'` or `mode: 'move'` | | `archive-move` | POST | Merge-moves a folder into a destination (merges contents if dest already exists) | --- ## Frontend (`FileSharing.jsx`) All FBQ calls go through the `fbqProxy()` helper which injects the current Supabase access token on every call. - **Single file download**: `download` action → signed URL → hidden `` click. - **Multi-file / folder download**: `download-blob` streams each file, then JSZip builds a ZIP client-side. - **Upload**: `upload-token` gets a short-lived FBQ token, then browser POSTs binary directly to `${fbqUrl}/api/resources?auth=${token}`. - **Root path** per user is derived by `rootPath()` in `FileSharing.jsx` — team/external get `/`, clients with one company get `/Clients/{name}`, clients with multiple get `/Clients`. - **Pinned folders** are persisted in `localStorage` keyed by `fbq_pins:{userId}`. - **Nav tree** lazily loads folder children into a `navCache` ref; does not re-fetch already-loaded paths. - **Drag-and-drop upload** uses `webkitGetAsEntry` to walk dropped folder trees, creates missing directories first, then uploads files. - **Context menu** (right-click): Open, Download, Pin/Unpin, Copy, Cut, Paste here, Delete. Delete in context menu is restricted to `role === 'team'` (`accessScope.canManage`). --- ## Required Env Vars ``` FILEBROWSER_URL=https://fourgebranding.krao.us FILEBROWSER_TOKEN= FILEBROWSER_ADMIN_USER=admin FILEBROWSER_ADMIN_PASS= FILEBROWSER_TEAM_ROOT=/fourgebranding FILEBROWSER_CLIENT_ROOT=/fourgebranding/Clients FILEBROWSER_SUBS_ROOT=/fourgebranding/team FILEBROWSER_CLIENTS_ROOT=/fourgebranding/Clients ``` `FILEBROWSER_TOKEN` is the primary auth method. `ADMIN_USER`/`ADMIN_PASS` are only used for auto-refresh when the token expires.