close
Skip to content

Releases: patillacode/dropit

v0.1.0

01 Jun 19:48

Choose a tag to compare

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:

  1. Remove UPLOAD_TOKENS from your .env / compose file.
  2. Sign in to /admin with your ADMIN_TOKEN.
  3. 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/users REST API (admin-only) exposes the full lifecycle:
    • GET /admin/users — list users (no token or hash exposed)
    • POST /admin/users — create user, returns token once
    • DELETE /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_TOKEN env 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_admin previously re-ran the full token lookup independently; it now composes on top of
    Depends(get_current_user).
  • verify_token accepts a TokenUser directly instead of raw credentials, avoiding a second DB hit.
  • TokenUser gains a user_id: int | None field (None for the break-glass admin).
  • hash_token and generate_token are 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:

  • .btn BEM design system — shared button base class, eliminates per-component button style duplication
    across base.css, admin.css, and index.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 both index.js and
    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

31 May 18:28

Choose a tag to compare

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-Length is 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 and www.dropit.patilla.es returned a 404
  • Admin auth hardening: token comparison now uses constant-time comparison; admin auth logic consolidated

Chores

  • Updated README link to point to the new tutorial/portfolio

v0.0.8

28 May 17:26

Choose a tag to compare

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

23 May 13:04

Choose a tag to compare

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 dropit aesthetic is preserved.

v0.0.5

23 May 12:33

Choose a tag to compare

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 /dropit skill for uploading HTML files directly from Claude Code sessions.

v0.0.4

23 May 12:31

Choose a tag to compare

v0.0.4

  • Fix port confusion: all references to port 52031 replaced with 8000 across Dockerfile, compose.yml, .env.example, settings defaults, and docs.

v0.0.3

23 May 12:30

Choose a tag to compare

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 /admin panel (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 /upload with optional ?ttl= parameter
  • GET /p/{id} serving raw HTML
  • GET /me for token verification
  • GET /config returning available TTLs for the current token
  • GET /health

Deployment

  • Docker image for amd64 and arm64 (Raspberry Pi 4/5)
  • Single compose.yml deployment, data persisted in a mounted volume