Access control — BirdLense Hub
How optional passwords split view, contribute, and admin capabilities. Default install: no password = full local trust.
Configuration keys
general:
# Full access: settings, feeder, system, processor restart
settings_password: ""
# Optional: labeling & exports without admin (empty = single-password mode)
contributor_password: ""
Rules:
- Both empty → legacy “open hub” (same as today for home labs).
- Only
settings_password→ one tier; unlock behaves as admin for all gated actions. - Both set →
verify-passwordreturnsrole:admin(matchessettings_passwordfirst) orcontributor.
Roles
| Role | Typical user | Scope |
|---|---|---|
| Viewer | Guest, read-only share | Browse UI, exports that stay public in your policy |
| Contributor | Volunteer labeler | Unknowns, species fixes, iNaturalist crop, dataset export, reports |
| Admin | Owner | Everything Contributor has plus settings, feeder dispense, storage purge, processor restart |
Exact UI gates follow settings_check_access() (admin) and contributor_or_admin_access() (contributor + admin) in code.
Permission matrix
Viewer (not unlocked)
| Action | Allowed |
|---|---|
| Overview, Timeline, Live, species pages | ✅ |
Play video on recording page (/api/ui/videos/:id/stream) |
✅ (unless general.require_auth_for_video_stream is true) |
| PDF report, timeline CSV/JSON/eBird (if you expose them without lock) | ✅* |
| Correct species / Unknowns | ❌ |
| iNaturalist export crop | ❌ |
| Feeder dispense | ❌ |
| Settings | ❌ |
| System (purge, scan, regenerate, logs…) | ❌ |
| Dataset ZIP export | ❌ |
*Depends on route-level checks; sensitive exports require Contributor+.
Contributor
| Action | Allowed |
|---|---|
| Everything Viewer | ✅ |
| Species correction, Unknowns | ✅ |
| iNaturalist | ✅ |
| Dataset export (where exposed to contributor) | ✅ |
| Feeder dispense | ❌ |
| Settings | ❌ |
| Destructive system actions | ❌ |
Admin
| Action | Allowed |
|---|---|
| Everything Contributor | ✅ |
Feeder POST /api/ui/feed/dispense |
✅ |
| Settings | ✅ |
| System tools, restart processor | ✅ |
Session
After successful POST /api/ui/settings/verify-password:
session['access_role'] = 'admin' | 'contributor'
session['settings_unlocked'] = True # admin path; contributor may differ
Server checks access_role on each gated request.
MCP: Valid Authorization: Bearer <MCP_TOKEN> can satisfy admin-level checks for automation (settings_check_access()), so protect tokens like root passwords.
Rate limiting (verify-password)
Brute-force protection on POST /api/ui/settings/verify-password:
| Rule | Value |
|---|---|
| Window | 60 seconds (rolling, per client IP) |
| Failed attempts | 5 wrong passwords → further requests get 429 until the window cools down |
| HTTP | 429 + JSON {"ok": false, "error": "Too many attempts"} + header Retry-After: 60 (seconds) |
| Success | A correct password clears the failure counter for that IP |
Client IP behind nginx: the app uses X-Real-IP, then the first address in X-Forwarded-For, then remote_addr (client_ip_for_rate_limit() in util.py). Nginx sets X-Real-IP and X-Forwarded-For for /api in nginx/standalone.conf.template. If Gunicorn is exposed without a trusted reverse proxy, clients could spoof these headers — restrict who can reach port 8000.
Tests: TestVerifyPasswordRateLimit in app/web/tests/test_api.py (run via make test-web).
Feeder API
POST /api/ui/feed/dispense requires settings_check_access() → Admin (or valid MCP Bearer where implemented). Otherwise 403.
Security notes
- Passwords in YAML are plain text today — prefer restricted file permissions; consider env-based secrets for production.
- Use HTTPS when exposing the UI beyond localhost so session cookies are not leaked.
- Processor and MCP use separate secrets (
PROCESSOR_SECRET,MCP_TOKEN).
Future ideas (not roadmap commitments)
Community / donation UX (leaderboards, “unlock with support”, badges) is listed under Future work candidates in ROADMAP. Config hook general.donate_url adds optional support links in the app chrome and Food card — see CONFIGURATION.
See also
CONFIGURATION · API · SECURITY · GLOSSARY