Releases: patillacode/dropit
v0.1.0
Summary
This release is a significant step up in maturity. The headline change is a full auth model replacement:
static plaintext tokens loaded from an environment variable give way to a proper database-backed user system
with hashed tokens, an admin CRUD API, and self-service token regeneration. Alongside that, the frontend stack
migrates from static HTML to Jinja2 templates, Google Fonts is dropped in favour of self-hosted WOFF2, and a
round of UI and code-quality work tightens up the whole codebase.
Breaking Changes
If you are upgrading from v0.0.9 or earlier, read this first.
The UPLOAD_TOKENS environment variable is gone. The old format (alice:token-here,bob:other-token) is
no longer parsed or respected. After upgrading:
- Remove
UPLOAD_TOKENSfrom your.env/ compose file. - Sign in to
/adminwith yourADMIN_TOKEN. - Recreate users from the admin panel — each user's token is shown once on creation.
Existing uploaded pages are unaffected.
What's New
1. DB-backed User Management
The core auth model has been replaced entirely.
Before (v0.0.9): tokens were stored in plain text in UPLOAD_TOKENS and parsed at startup into an
in-memory dict. There was no way to add/remove users without restarting the process and editing .env.
After (v0.1.0): a User table in SQLite stores users with their SHA-256-hashed token, name, admin flag,
and creation timestamp.
- Tokens are generated as 32-char hex strings (
secrets.token_hex(16)) and stored hashed — the plaintext is
shown exactly once at creation time and cannot be retrieved again, only regenerated. - A new
/admin/usersREST API (admin-only) exposes the full lifecycle:GET /admin/users— list users (no token or hash exposed)POST /admin/users— create user, returns token onceDELETE /admin/users/{id}— delete user (blocked if deleting yourself; pages are preserved)POST /admin/users/{id}/regenerate— rotate token, immediately invalidating the old one
- The break-glass
ADMIN_TOKENenv var is retained for bootstrapping and disaster recovery. It is not stored
in the DB and cannot be rotated from the UI.
2. Self-Service Token Regeneration
Any authenticated user can regenerate their own token from the main upload page. The new token must be
re-pasted everywhere the old one was used (other browsers, devices, CLI).
3. Admin Panel — Users Management UI
The admin panel gains a Users section:
- A stat box shows total user count in the Overview.
- The layout moves to a responsive 2/4-column grid.
- A full users table with connect (copy token URL) button and delete action behind a confirmation modal.
- All dangerous actions require a confirmation step.
4. Jinja2 Template Engine
Static HTML files with __PLACEHOLDER__ string replacement are replaced by real Jinja2 templates:
| Old | New |
|---|---|
app/static/admin.html |
app/templates/admin.html |
app/static/error.html |
app/templates/error.html |
app/static/index.html |
app/templates/index.html |
| (none) | app/templates/base.html (shared layout) |
A shared base.html eliminates duplicated <head>, font loading, and favicon markup across pages. Autoescape
is enabled.
5. Self-Hosted Fonts
Google Fonts is removed. Six WOFF2 files are now bundled under app/static/fonts/:
- Cormorant Garamond: regular, 300, 300 italic
- DM Mono: regular, 300, 300 italic
A new app/static/css/fonts.css declares the @font-face rules. Benefits: no external DNS lookup to Google
on page load, works fully offline, removes a third-party dependency.
6. Error Handling Consolidation
A new app/errors.py module introduces error_response(detail, status_code). Previously, main.py contained
three separate copy-pasted blocks that built 404 HTML via string replacement — one in the catch-all route,
one in the HTTP exception handler, and one inside the subdomain middleware. All three are now a single
error_response(exc.detail) call.
The error template is rendered via Jinja2 with a message lookup table; unknown details fall back to a generic
"doesn't exist" copy.
7. Auth Refactor
auth.py is cleaner and removes duplication:
require_adminpreviously re-ran the full token lookup independently; it now composes on top of
Depends(get_current_user).verify_tokenaccepts aTokenUserdirectly instead of raw credentials, avoiding a second DB hit.TokenUsergains auser_id: int | Nonefield (None for the break-glass admin).hash_tokenandgenerate_tokenare public helpers, making them testable directly.
8. Settings Cleanup
UPLOAD_TOKENS and all related parsing logic (parse_tokens, token_map property) have been removed from
app/settings.py. The Settings class is now ~17 lines shorter.
9. Frontend & CSS Improvements
Several UI quality-of-life changes land in this release:
.btnBEM design system — shared button base class, eliminates per-component button style duplication
acrossbase.css,admin.css, andindex.css.- Dot-drop animation — animated indicator on the drop zone.
- Custom checkbox — replaces the browser default.
- Accent class — reusable highlight utility.
- Expires label — improved display of expiry metadata.
- Button-style token actions — token copy/regenerate controls now look like buttons.
- Right-aligned token indicator — restored alignment fix for token action buttons.
10. JavaScript Refactor
Token field handling is extracted into two new ES modules:
token-shared.js— shared utilities (field state, clipboard, indicator logic) used by bothindex.jsand
admin.js.token-modal.js— modal flow for token display / confirmation.
admin.js grows substantially (+~200 lines net) to support the new users table, connect/delete flows, and
modal confirmations.
11. Favicon
An SVG favicon (app/static/favicon.svg) is added to all pages via the shared base template.
Tests
Three test files are added or rewritten:
| File | Before | After | Notes |
|---|---|---|---|
tests/test_auth.py |
Monkeypatched settings, no DB | In-memory SQLite session fixture | Tests now hit a |
| real schema | |||
tests/test_users.py |
(did not exist) | 104 lines | Full CRUD coverage: list, create, duplicate, |
tests/test_errors.py |
(did not exist) | 20 lines | Covers expired-page copy, generic copy, and custom |
| status code |
Configuration Changes
| Variable | v0.0.9 | v0.1.0 |
|---|---|---|
UPLOAD_TOKENS |
Required — name:token,... list |
Removed |
ADMIN_TOKEN |
Optional | Still optional but now the only way to bootstrap |
.env.example is updated accordingly.
v0.0.9
v0.0.9
Features
- Clickable upload URL: the success URL is now a direct link that opens in a new tab, replacing the separate Copy button
- Filename and upload date in admin: the admin table now shows the original filename and upload timestamp; the
index page history list also displays the date. Existing rows show for both fields
Fixes
- Upload size enforcement:
Content-Lengthis now checked early and chunked reads are capped at
max_upload_size, preventing oversized uploads from slipping through - Content subdomain middleware: static assets and reserved subdomains (
www, etc.) are now passed through
correctly; previously, CSS failed to load on error pages andwww.dropit.patilla.esreturned a404 - Admin
authhardening: token comparison now uses constant-time comparison; adminauthlogic consolidated
Chores
- Updated
READMElink to point to the new tutorial/portfolio
v0.0.8
v0.0.8
⚠️ Breaking Change: existing shared links will stop working
Pages are now served from per-page subdomains ({page_id}.{CONTENT_DOMAIN}) instead of the old /p/{page_id} path. That route is gone and there's no redirect, so any links shared before this release will 404. You'll need to re-upload and re-share them.
Why the change: serving user-supplied HTML on the same origin as the app lets a malicious page steal localStorage tokens. Putting each page on its own subdomain cuts that off entirely.
What's New
Per-page subdomain serving (origin isolation)
Each uploaded page now lives at {page_id}.{CONTENT_DOMAIN}. Security headers are applied to app routes (CSP, X-Frame-Options, etc.) and content pages get noindex and no-store too. Uvicorn now runs with --proxy-headers so the host resolves correctly behind a reverse proxy.
Auto-upload drop zone
The upload UI was fully rebuilt. Drop a file or pick one from the browser and it uploads straight away, no separate button needed. The zone moves through a few states:
- idle: waiting for a file
- uploading: shows a progress indicator
- success: shows the URL with click-to-copy and an "Upload another" button to reset
- error: shows an inline message with a click-to-retry hint
The old result and error panels below the zone are gone.
Cleanup scheduler card in admin UI
The admin panel now has a cleanup scheduler section. It shows the last run time, how many pages were deleted, what triggered it (scheduled or manual), when the next run is, and a collapsible history table. There's also a "Run now" button if you want to trigger it manually.
Custom 404 page
Unknown routes now return a proper styled HTML 404 page instead of a raw JSON error.
Bug Fixes
Mixed-case page IDs now resolve correctly
Older page IDs were generated with token_urlsafe, which can include uppercase letters (like sRysndGq). Browsers lowercase hostnames before sending them, so the subdomain would arrive as srysndgq and the case-sensitive DB lookup would miss it. The lookup now lowercases both sides and resolves the file path from the stored ID, so old pages with uppercase IDs still work fine.
Refactoring
Inline <style> and <script> blocks in index.html, admin.html, and error.html were moved out into separate files under app/static/css/ and app/static/js/.
v0.0.6
v0.0.6
Custom 404 pages
- Expired and missing page links now show a branded error page instead of a raw JSON response.
- Expired links display "This page has expired", unknown links display "This page doesn't exist".
- API routes are unaffected and continue returning JSON.
- The
dropitaesthetic is preserved.
v0.0.5
v0.0.5
Features
- GHCR publishing: Docker images are now also pushed to GitHub Container Registry on every release,
in addition to the Forgejo registry. Both tagged versions and latest are published. - GitHub link in footer: The "Open source" text in the footer now links to the GitHub repository.
Documentation
- Added Claude Code integration section: documents the
/dropitskill for uploading HTML files directly from Claude Code sessions.
v0.0.4
v0.0.4
- Fix port confusion: all references to port
52031replaced with8000acrossDockerfile,compose.yml,.env.example, settings defaults, and docs.
v0.0.3
Initial release of drop•it: a self-hosted HTML file sharing with short-lived public URLs.
What's included
Core
- Upload an HTML file, get a shareable URL with a configurable TTL (1h, 6h, 24h, 48h, 7d)
- Automatic cleanup of expired pages on a configurable schedule
- Bearer token auth with named upload tokens
Admin
- Admin token with access to the
/adminpanel (list and delete all uploads) - forever TTL option: admin-only permanent uploads
- Per-user TTL cap: non-admin tokens can be restricted to a maximum expiry
UI
- Drag-and-drop landing page — enter token once, upload, copy link
- Recent uploads history (last 5, persisted in localStorage)
- Admin panel at /admin — full upload table with delete controls
API
- POST
/uploadwith optional?ttl=parameter - GET
/p/{id}serving raw HTML - GET
/mefor token verification - GET
/configreturning available TTLs for the current token - GET
/health
Deployment
- Docker image for amd64 and arm64 (Raspberry Pi 4/5)
- Single
compose.ymldeployment, data persisted in a mounted volume