A group can now be given locations as well as single printers: it then
reaches every printer in them, including printers added there later.
Printer access moves out of the group editor to Settings ->
Authentication -> Printer access, built for large fleets: groups and
printers searchable and filtered by location, model and access, edited
by group or by printer, saved together. "Who has access" in the printer
card menu opens it on that printer.
- group_locations table; the scope is picked printers plus printers
whose location matches.
- Moving a printer into or out of a location a limited group has is
admin-only, and open connections are rescoped. The edit dialog names
the groups a move affects. Locations are trimmed on save.
- Clearing a printer's location in the edit dialog now clears it.
A group can now be limited to a set of printers. Its members see and
control only those printers. Every other printer answers 404, as if it
didn't exist.
- Groups gain restrict_printers and a group_printers table (migration
for SQLite and Postgres). A user's printers are the union of their
limited groups. Groups without the flag don't limit anything, a user
in no limited group keeps every printer, and admins see all of them.
- core/printer_scope.py holds the scope. RequestPrinterScope and
RequirePrinterPermissionIfAuthEnabled apply it to routes: printer
routes, camera, queue and batches, archives, projects, stats, print
log, pipeline runs, inventory and Spoolman assignments, maintenance,
smart plugs, scheduled drying, firmware and Obico status.
- API keys, camera stream, Cam Wall, overlay and WebSocket tokens carry
the printers of whoever created them. WebSocket broadcasts are
filtered per connection, and the filtering fails closed.
- Scheduler: "Any <model>" jobs stay on their owner's printers. A job
pinned to a printer its owner lost waits with a reason. Callers with
no user identity and limited printers must queue to a specific printer.
- Group editor: new Printer access section, translated into all 15
locales. Saving a system group no longer resends unchanged
permissions, which the backend refused.
Implement a full permissions system replacing simple admin/user roles:
Backend:
- Add Group model with many-to-many user relationship
- Add 50+ granular permissions (resource:action pattern)
- Create default groups: Administrators, Operators, Viewers
- Add permission-checking dependencies for route protection
- Add groups API endpoints (CRUD, user assignment)
- Add change password endpoint for users
- Update backup/restore to include groups
- Migrate existing users to groups on startup
Frontend:
- Add GroupsPage for managing groups and permissions
- Add permission helpers to AuthContext (hasPermission, hasAnyPermission)
- Add PermissionRoute component for protected routes
- Disable buttons/features based on permissions (with tooltips)
- Add change password modal in sidebar for all users
- Add forgot password info modal on login page
- Show user groups in UsersPage with group assignment
Testing:
- Add integration tests for groups API
- Add tests for user-group assignments
- Add tests for change password endpoint
- Seed default groups in test fixtures
Closes#28#161