Secure, self-destructing file sharing built with Nuxt and Supabase.
007-Drop encrypts files in the browser before upload, shares the decryption key in the URL fragment (#...), and removes files after limits are reached or expiration time passes.
- Client-side AES-256-GCM encryption via Web Crypto API
- Optional password protection (PBKDF2-derived key combined with URL key)
- One-time, fixed-count, or unlimited downloads
- Automatic expiry (24 hours)
- Optional encrypted image preview thumbnails
- Discord/Slack-style webhook notifications on download
- Local "recent uploads" dashboard with delete support
- QR code generation for quick sharing
Nuxt 4+Vue 3@nuxtjs/supabaseSupabase Storage+Supabase PostgresJSZipfor multi-file uploadsqrcodefor share QR generation@vueuse/corefor clipboard/local storage utilities
- File is encrypted in the browser.
- Encrypted bytes are uploaded to Supabase Storage.
- Metadata is stored in the
filestable. - Share link format:
/d/<id>#<key> - Receiver downloads encrypted bytes and decrypts in browser.
- File is cleaned up after expiration or download limit.
npm installCopy .env.example to .env:
cp .env.example .envSet the following values:
SUPABASE_URL=your-project-url
SUPABASE_KEY=your-anon-key
SUPABASE_SERVICE_KEY=your-service-role-key
CRON_SECRET=your-random-cleanup-secretCreate a storage bucket named drops.
Create a table named files:
create table if not exists public.files (
id uuid primary key,
original_name text not null,
storage_path text not null unique,
file_size bigint not null,
mime_type text not null,
download_limit integer not null default 1, -- 0 = unlimited
download_count integer not null default 0,
expires_at timestamptz not null,
is_password_protected boolean not null default false,
password_salt text,
has_preview boolean not null default false,
delete_token uuid not null,
webhook_url text,
created_at timestamptz not null default now()
);
create index if not exists files_expires_at_idx on public.files (expires_at);npm run devVisit http://localhost:3000.
The endpoint GET /api/cron/cleanup deletes expired files and records.
Authenticate using either:
Authorization: Bearer <CRON_SECRET>- or query param
?key=<CRON_SECRET>
Example:
curl -H "Authorization: Bearer $CRON_SECRET" \
http://localhost:3000/api/cron/cleanupSchedule this endpoint with your preferred cron service.
POST /api/upload- upload encrypted file + metadataGET /api/file/:id- fetch file metadata and availabilityGET /api/download/:id- increment count + return signed download URLGET /api/preview/:id- return encrypted thumbnail bytes (if available)DELETE /api/file/:id- delete a file (requiresx-delete-token)GET /api/cron/cleanup- cleanup expired/consumed files
- Encryption and decryption happen client-side.
- Decryption key is stored in URL fragment, not sent to server.
- Optional password adds a second key factor.
- Service role key must never be exposed to the client.
npm run dev- start local dev servernpm run build- production buildnpm run preview- preview production buildnpm run generate- static generation
Deploy as a Nuxt/Nitro app (Node adapter or compatible hosting), configure env vars, and schedule GET /api/cron/cleanup.