Adds .gitea/workflows/docker-build-push.yml (build+push to
gitea.blairhaus.net/pmb/finger-web, matching yttrx-welcomebot's pattern
for the same admin.yttrx.com deploy target) now that GitHub is retired.
Overrides Bootstrap 5.3's native dark color-mode tokens with the exact
Tokyo Night hex values waffles.yttrx.com's Compost/Tailwind theme uses
(same primary blue, neutral backgrounds, etc.), so the two sites share
one visual identity, plus repoints the stale GitHub source link to Gitea.
The search form POSTed to /finger, an uncacheable URL with no search term in
it, so every lookup re-ran the finger command even for repeated searches of the
same user. Redirect any query/POST search to the canonical /finger/<username>
path (and submit the form via GET, with a JS fast-path straight to that URL) so
repeated lookups are served from the nginx response cache. A bare /finger with
no user still lists local system users.
The app sits behind nginx, which caches 200s for 30s — so repeated lookups
of the same user are cheap. What bypasses the cache is enumeration of distinct
usernames: each is a unique cache key -> miss -> a fresh finger call to the
mammut daemon, all attributed to admin's single IP (so the daemon cannot ban
the real source). The app only ever receives cache misses, so a per-IP limit
here throttles exactly that uncached path without touching the cached hot path.
- Flask-Limiter keyed per client IP: 30/min on the finger lookup endpoints,
10/min on /api/upload (auth brute-force), 120/min global default. Index and
the container healthcheck are exempt. All limits env-tunable (RATELIMIT_*).
- ProxyFix(x_for=1): trust nginx's X-Forwarded-For so the real client IP is
used for keying and logging. Without it the app only saw the Docker bridge
gateway (172.20.0.1) and every client shared one bucket.
- 429 handler (JSON for /api, HTML 429.html otherwise) and WARNING logging of
failed/invalid lookups and limit hits, so enumeration is observable.
Unify the interactive /finger page on the same results-first layout
introduced for direct /finger/<user> links: drop the two-column view
(About Finger panel, success banner, timestamp, back-to-home button)
and the now-unused direct flag.
Direct links now render a results-first single-column view: drop the
About Finger panel, success banner, timestamp and back-to-home button,
and replace the lookup card with a small inline form next to the heading.
The interactive /finger page keeps its original two-column layout.
base.html referenced url_for('api_hello'), but that endpoint is now
api_info (/api/info). Since every page extends base.html, the dead
reference raised werkzeug BuildError and returned HTTP 500 site-wide --
including /finger/<user>, which federated Mastodon instances fetch for
link previews. The 500s also defeated nginx's 200-only cache, so every
fetch re-ran the finger subprocess against the daemon on mammut.
- Add /api/finger and /api/finger/<username> JSON endpoints
- Remove hardcoded default credentials from config.py; require BASIC_AUTH_USERS env var
- Add cli/finger.py: query and plan-upload CLI using the JSON API
- Add mcp/server.py: FastMCP server exposing finger_user and upload_plan tools
- All credentials and base URL are read from environment variables