Three issues from the post-merge audit of the unified-dispatch PR, all
pre-existed on dev but became more impactful once every print routes
through the queue:
1. Start/Stop ownership gates. /queue/{id}/stop required QUEUE_UPDATE_ALL
(admin-only) -- operators saw the Stop button in the queue UI but got
403 on click. /queue/{id}/start required QUEUE_UPDATE_OWN with no
ownership check -- _OWN holders could start anyone's queue items via
direct API. Both routes now use require_ownership_permission, mirroring
/cancel. Stop is strict (rejects unowned items for _OWN); start preserves
#1670's VP-import flow where _OWN can start NULL-owner items and claim
ownership at click-time. Frontend QueuePage Start/Stop buttons flip
from printers:control to canModify('queue', 'update', created_by_id).
2. TOCTOU race on insert_position. Concurrent ASAP inserts to the same
scope both computed MAX(position) from before the other committed; in
an empty scope, both inserted at position=1 (duplicate). Wraps the
read+update in a transaction-scoped Postgres pg_advisory_xact_lock
keyed on the printer_id. Different printers don't contend. SQLite
serializes writes implicitly so the path is no-op there. Dialect is
checked against the live session binding, not the is_sqlite() helper,
because the test fixture overrides get_db to SQLite while
settings.database_url still points at Postgres.
3. /reorder duplicate-position validator. POST /queue/reorder set position
from the payload in a loop with no uniqueness validation -- a buggy
drag-drop client could leave the queue with ambiguous ordering (the
scheduler's ORDER BY (printer_id, position) ties break by row order).
New model_validator on PrintQueueReorder rejects duplicates at the
schema layer with 422 + "Duplicate positions in reorder request: [N, ...]".
## Summary
Address two critical security issues reported via GitHub Security Advisory:
1. Hardcoded JWT secret key allowing token forgery
2. Missing authentication on 77+ API endpoints
## Changes
### JWT Secret Key (backend/app/core/auth.py)
- Remove hardcoded secret "bambuddy-secret-key-change-in-production"
- Load secret from JWT_SECRET_KEY environment variable (recommended)
- Fall back to .jwt_secret file in data directory (auto-generated)
- Generate cryptographically secure 64-byte random secret if neither exists
- File is created with 0600 permissions for security
### API Authentication Middleware (backend/app/main.py)
- Add HTTP middleware that enforces auth on ALL /api/ routes
- When auth is enabled, every API request requires valid JWT or API key
- Only exempt routes that must be public:
- /api/v1/auth/status (check if auth enabled)
- /api/v1/auth/login (login endpoint)
- /api/v1/updates/version (version check)
- /api/v1/ws/* (WebSockets handle own auth)
### Test Updates
- backend/tests/conftest.py: Patch middleware's async_session for tests
- backend/tests/integration/test_ownership_permissions.py: Add missing
auth headers to requests that now require authentication
## Migration Notes
- Existing JWT tokens will be invalidated (users must re-login)
- Set JWT_SECRET_KEY env var in production for token persistence across restarts
- No database changes required
Fixes: GHSA-gc24-px2r-5qmf
Security: CWE-306 (Missing Authentication), CWE-321 (Hardcoded Crypto Key)
Closes GHSA-gc24-px2r-5qmf
Backend:
- Split update/delete permissions into *_own and *_all variants:
- queue:update_own/all, queue:delete_own/all
- archives:update_own/all, archives:delete_own/all, archives:reprint_own/all
- library:update_own/all, library:delete_own/all
- Add require_ownership_permission dependency factory in auth.py
- Enforce ownership checks on all relevant API endpoints:
- archives.py: PATCH, DELETE, POST /reprint
- print_queue.py: PATCH, DELETE, POST /cancel, PATCH /bulk
- library.py: PUT /files, DELETE /files, POST /bulk-delete, DELETE /folders
- Add user items count endpoint: GET /users/{id}/items-count
- Add delete_items parameter to DELETE /users/{id}
- Explicitly set created_by_id to NULL on user deletion for DB portability
- Add permission migration for existing groups in database.py
- Add require_permission_if_auth_enabled for folder delete
Frontend:
- Add canModify helper to AuthContext for ownership-based checks
- Update ArchivesPage: use canModify for edit/delete/reprint buttons
- Update QueuePage: use canModify for edit/delete/cancel buttons
- Update FileManagerPage: use canModify for edit/delete buttons
- Update SettingsPage: add user deletion modal with item handling options
- Update StatsPage: use archives:update_all for recalculate costs
- Update Permission type with new ownership permissions
- Add getUserItemsCount and update deleteUser API methods
Tests:
- Add test_ownership_permissions.py with 28 comprehensive tests
- Test admin *_all permissions, operator *_own permissions
- Test bulk operations skip non-owned items
- Test auth disabled allows all operations
- Test user deletion with/without items
Closes#205