125 Commits

Author SHA1 Message Date
zurdi
e6225e582a refactor(permissions): replace get_permissions checks with assert_platform_visible and assert_rom_visible for better clarity and maintainability 2026-06-29 10:11:02 +00:00
zurdi
7d45795408 fix(permissions): address gantoine review + Postgres-safe single migration
Visibility-coverage gaps (404-mask hidden entities, mirroring the existing
delete/read paths):
- update_rom (PUT /roms/{id}) and update_rom_user (PUT /roms/{id}/props)
- add_firmware: platform-hide now cascades to firmware uploads
- patch_rom: resolve the parent rom of both the base and patch files and
  404 when hidden, so a hidden rom's bytes can no longer be streamed back
- activity feeds (get_all_activity / get_rom_activity): drop sessions whose
  rom is hidden from the caller

Migration: make the role enum -> varchar narrowing Postgres-safe. The cast
now uses postgresql_using, the orphaned native role type is dropped on
upgrade, and downgrade recreates it explicitly (create_type=False) before
re-typing the column. Verified up/down/up on Postgres 16 and MariaDB.

Also collapses the two permission migrations into a single 0092 and notes
the override own_only replacement granularity limit in the resolver.

AI assistance: implemented with Claude Code (review-fix pass).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 22:10:00 +00:00
zurdi
4e686e1d74 fix(permissions): address PR review (Copilot + adversarial) findings
Resolves the CI blocker and a cluster of opt-out visibility "fail-open"
gaps surfaced in review of the granular permission system.

Security / correctness:
- admin oauth_scopes projection keeps canonical FULL_SCOPES order
  (order_scopes) instead of sorting alphabetically, fixing the red
  test_user.py::test_admin on MariaDB + Postgres.
- default-group hides no longer fail open: the resolver resolves the
  effective (own-or-default) group before the hidden-entity lookup.
- /roms/by-hash and /roms/by-metadata-provider now 404-mask hidden roms.
- USERS-entity grant no longer enables admin creation: add_user and
  invite-link require a real admin to mint admin accounts.

Visibility leaks closed on secondary read paths:
- feeds, sibling roms (list query + single-rom schemas), /stats counts
  and per-platform breakdowns, collection rom_ids/rom_count, search_rom.

Hardening / cleanups:
- firmware/platform PUT 404-mask hidden entities; group rename conflict
  returns 400 not 500; guard against removing the last default group;
  kiosk read-only enforced at the fine layer; add_hidden_entity rejects
  non-cascading entity types.

Frontend:
- permissionGroups.ensureLoaded coalesces concurrent callers on one
  in-flight request; permissions.setGrants resets isAdmin/hidden;
  CreateUserDialog no longer orphans a user when group assignment fails;
  HiddenGamesPicker search rows are native buttons (keyboard/gamepad);
  invite-role labels and group swatch aria-label use i18n; drop dead code
  (originalRole, unused permissionsApi export).

AI assistance: changes authored with Claude Code (Claude Opus), driven by
the Copilot review and a multi-agent adversarial review, then verified
(backend pytest, frontend typecheck/vitest, i18n parity, trunk).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 12:37:58 +00:00
zurdi
bf801cb314 feat(permissions): narrow user role to admin/user and adapt v1
Collapse the Role enum from viewer/editor/admin to two kinds (admin/user);
non-admin access now comes entirely from permission groups + overrides.

Backend:
- Role -> StrEnum {USER, ADMIN}, VARCHAR-backed (native_enum=False); Role.coerce
  maps legacy/unknown strings (incl. in-flight invites) to USER.
- Resolver: group-less users fall back to the default group (dropped the
  role-based legacy fallback); kiosk caps all non-admins to read-only.
- OIDC editor/viewer claims both map to USER (env var names unchanged).
- Migration 0092 converts the native enum to VARCHAR and normalizes
  ADMIN->admin, VIEWER/EDITOR->user.
- Updated endpoints, conftest fixtures, role/oidc/parity/db-handler tests, tools.

Frontend:
- v2 + v1 user management now use admin/user (dropdowns, defaults, getRoleIcon).
- Regenerated types (Role = 'user' | 'admin'); added role-user i18n to all locales.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 22:42:51 +00:00
zurdi
649ddce2aa feat: Implement fine-grained permission checks and admin CRUD for permission groups
- Added new permission handling in `backend/handler/auth/dependencies.py` to support fine-grained, DB-backed permission checks.
- Enhanced user role update logic in `backend/endpoints/user.py` to prevent demotion of the last admin.
- Introduced `hidden_platform_ids` and `hidden_rom_ids` parameters in various database handlers to manage visibility based on admin settings.
- Created new endpoints for managing permission groups, user memberships, and hidden entities in `backend/tests/endpoints/test_permissions_admin.py`.
- Added tests for permissions visibility and CRUD operations in `backend/tests/endpoints/test_permissions_visibility.py` and `backend/tests/endpoints/test_permissions_me.py`.
- Updated archive handling in `backend/utils/archives.py` to improve error logging and timeout management during extraction.
2026-06-24 08:45:45 +00:00
zurdi
a730173de6 feat: redesign permission system to support group-based granular permissions
- Introduced a new permission model with `PermissionGroup`, `UserPermissionOverride`, and `HiddenEntity` to manage access control.
- Added `DBPermissionsHandler` for handling permission-related database operations.
- Updated `User` model to include a foreign key to `PermissionGroup` and modified `oauth_scopes` to derive from the new permission model.
- Implemented tests to ensure the new permission model maintains parity with legacy access controls.
- Created documentation outlining the new permission system architecture and migration strategy.
2026-06-23 14:04:34 +00:00
Georges-Antoine Assi
2631f0e45d Merge pull request #3577 from Spinnich/feature/oidc-allow-registration
feat(auth): add OIDC_ALLOW_REGISTRATION toggle to gate OIDC auto-registration
2026-06-22 18:06:13 -04:00
Spinnich
11a81c756f feat(auth): add OIDC_ALLOW_REGISTRATION toggle
Gate automatic account creation on OIDC login behind a new OIDC_ALLOW_REGISTRATION environment variable. Defaults to true, preserving the current auto-provisioning behavior; set it to false to run OIDC in an "existing users only" mode, where a login from an email without an existing RomM account is rejected with a 403 instead of silently creating one. Existing users are unaffected either way.

Adds the config constant, env.template and docs entries, and tests covering the enabled/disabled and existing-user paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 12:33:00 +00:00
Georges-Antoine Assi
77970f6379 Merge branch 'master' into feature/device-flow-authorization 2026-06-21 16:25:39 -04:00
zurdi
78eef900b9 feat: update logs endpoint to require 'logs.read' scope and adjust related authorization checks 2026-06-20 09:55:10 +00:00
nendo
519abc1645 Add device authorization flow for TV-app-style pairing (RFC 8628)
Implements RFC 8628-style device authorization so clients
(argosy-launcher, grout) can pair by display instead of manually
copying tokens. Device posts to an open /api/auth/device/init with
its identifier and requested scopes; the server returns device_code
+ user_code + QR URL. User scans QR, lands at /pair/device, approves
(optionally editing name/scopes/expiry); the device's next poll on
/api/auth/device/token returns a ClientToken bound 1:1 to a newly-
created (or deduped) Device record. Downstream endpoints
(/play-sessions, /sync/negotiate) infer device_id from the bound
token so the client doesn't have to ship it on every call.

- Migrations 0080/0081: devices.client_device_identifier (unique
  per user) and client_tokens.device_id FK (ON DELETE SET NULL)
- Five new endpoints under /api/auth/device (init/pending/approve/
  deny/token) with Redis-backed state, per-IP rate limits, and
  RFC-compliant error codes (authorization_pending, slow_down,
  expired_token, access_denied)
- HybridAuthBackend surfaces bound device_id on request.state and
  bumps devices.last_seen with a 5-minute debounce
- /api/users/me returns current_device_id for bound tokens so a
  device can identify itself from its token alone
- Frontend approval screen at /pair/device with editable scopes/
  name/expiry (defaults to Never), 3s auto-close countdown
- ClientApiTokens settings list shows bound-device chip
- 20 i18n keys added to all 17 locales; generated models updated
- 52 new tests across 13 classes; full suite 1334 passed

Planning and review assisted by Claude Code.
2026-06-18 05:24:32 +09:00
copilot-swe-agent[bot]
a2775ca2b8 fix: handle malformed authorization header in hybrid auth backend
Co-authored-by: zurdi15 <34356590+zurdi15@users.noreply.github.com>
2026-06-06 22:22:41 +00:00
Georges-Antoine Assi
be476cb7dc Only set CSRF cookie on http.response.start
ASGI spec only allows headers on the http.response.start message;
appending Set-Cookie to body messages is out-of-spec and may break on
some servers. Early-return for non-start messages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 15:46:50 -04:00
copilot-swe-agent[bot]
f94206aa53 Refresh CSRF cookie when auth user changes
Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-05-24 14:46:31 +00:00
Georges-Antoine Assi
997e2c44aa start pre-4.8 cleanup 2026-03-12 23:02:12 -04:00
nendo
ea5b7546aa refactor: address PR #3114 review feedback
- Use atomic getdel for pairing code exchange
- Add cascade="all, delete-orphan" to User.client_tokens
- Move generate/hash_client_token into AuthHandler as static methods
- Extract endpoint helpers to utils/client_tokens.py
2026-03-11 10:56:35 +09:00
nendo
e0b25fbc6c feat(client-tokens): add client API tokens with QR pairing flow
Long-lived, revocable, scope-restricted tokens for external clients
(mobile apps, retro handhelds, third-party tools). Includes:

- Backend: model, migration, DB handler, auth integration (rmm_ prefix
  routing in HybridAuthBackend), CRUD + pairing + exchange endpoints,
  rate limiting, scope intersection enforcement, admin oversight
- Frontend: settings page with token management table, stepped
  create/deliver dialog (config -> copy/pair), QR code with RomM logo,
  admin token table, standalone /pair page for QR scan landing
- /pair page supports custom-scheme callbacks for app deep linking,
  falls back to displaying code for manual entry
- 33 backend tests across 5 classes (CRUD, auth, isolation, pairing,
  admin)
2026-03-11 10:56:35 +09:00
Georges-Antoine Assi
f6eb686559 fix tests 2026-03-10 08:38:43 -04:00
HydroSulphide
b9ea937373 Merge branch 'master' into fix-oauth-token-expiry-and-refresh-rotation 2026-03-10 08:51:44 +01:00
HydroSulphide
fd788684b9 fix: TOCTOU race condition allows duplicate refresh token use 2026-03-10 08:38:32 +01:00
HydroSulphide
02336974a6 Implemented greptile suggestions 2026-03-10 08:04:07 +01:00
HydroSulphide
8758cb31b7 Tried to fix everything the bot complained about and the failed pytests.
Three tests were also implemented to check initial implementation that now invalidates expired access and refresh tokens and also rotating refresh tokens.

Since I introduced wrapper functions for create_oauth_token to distinguish between access and refresh token there is no need to set the token type in the data dict, since the type is now enforced in the wrapper functions create_access_token and create_refresh_token.

By convention I renamed create_oauth_token to _create_oauth_token as it is considered a private helper function now.
2026-03-10 07:22:02 +01:00
copilot-swe-agent[bot]
2a7c86e304 Fix OIDC login downgrading existing user roles when no claims provided
Co-authored-by: pacnpal <183241239+pacnpal@users.noreply.github.com>
2026-03-09 18:26:49 +00:00
HydroSulphide
ad09babce8 fix: catch http exception on code 401 so api call with expired access token doesn't lead to internal server error 2026-03-09 18:29:28 +01:00
HydroSulphide
41f64eb42b fix: oauth token invalidation on expiration date and rotating refresh token 2026-03-09 17:03:29 +01:00
Georges-Antoine Assi
e2ece6b938 run fmt 2026-03-08 22:54:58 -04:00
copilot-swe-agent[bot]
53b0b9021b Switch invite token expiration unit from minutes to seconds
Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-03-09 02:48:21 +00:00
copilot-swe-agent[bot]
5f309639af Make invite token expiration configurable via env var and UI
Co-authored-by: gantoine <3247106+gantoine@users.noreply.github.com>
2026-03-09 01:43:53 +00:00
nendo
36eec298d1 Add device-based save synchronization
Implement device registration and save sync tracking to enable
multi-device save management with conflict detection.

- Device CRUD endpoints (POST/GET/PUT/DELETE /api/devices)
- Save sync state tracking per device
- Conflict detection on upload (409 when device has stale sync)
- Download sync tracking (optimistic and confirmed modes)
- Track/untrack saves per device
- DEVICES_READ/WRITE scopes for authorization
2026-01-18 16:50:44 +09:00
Georges-Antoine Assi
baddeea972 type ignore 2025-12-08 12:58:01 -05:00
Georges-Antoine Assi
85bf51d088 raise error is prefered username is none 2025-12-08 12:57:20 -05:00
Georges-Antoine Assi
50ddb42c25 [ROMM-2748] Add OIDC_USERNAME_ATTRIBUTE 2025-12-08 10:27:15 -05:00
Georges-Antoine Assi
f2774b9395 Merge pull request #2716 from rommapp/redis-backed-sessions
Add redis-backed session middleware
2025-11-22 18:40:43 -05:00
Georges-Antoine Assi
ac43b0aa94 changes from bot review 2025-11-22 11:22:45 -05:00
Georges-Antoine Assi
615e3132e4 changes from bot review 2025-11-22 10:55:14 -05:00
Georges-Antoine Assi
27e02fa2a2 consume the token as soon as its read 2025-11-22 10:51:41 -05:00
Georges-Antoine Assi
ec6bb24662 Add new redis-backed session middleware 2025-11-22 10:47:59 -05:00
Georges-Antoine Assi
4f6442a6ad catch typeerror in csrf token and return false 2025-11-18 16:56:10 -05:00
Georges-Antoine Assi
156d31b62a Fix CSRF failure on first admin signup 2025-11-18 14:04:47 -05:00
Georges-Antoine Assi
91ad9f7b7f fix trunk check issues 2025-11-18 10:12:58 -05:00
Georges-Antoine Assi
ee39fe1aba changes from bot review 2025-11-18 10:09:00 -05:00
Georges-Antoine Assi
d1824bf894 manually fix tests 2025-11-18 00:00:49 -05:00
Georges-Antoine Assi
6a1a344ba2 add tests for middlewares 2025-11-17 23:40:00 -05:00
Georges-Antoine Assi
551ff72a8a implement csrf middleware directly in repo 2025-11-17 21:12:29 -05:00
Tarow
cb2015fc4d fix: type error when role claim is null 2025-10-19 11:49:31 +02:00
Georges-Antoine Assi
154df816cf only set algos on decode calls 2025-10-03 12:54:42 -04:00
Georges-Antoine Assi
d863ca9e90 Explicitly set supported algorithms on jwt encode/decode 2025-10-03 11:18:01 -04:00
Georges-Antoine Assi
ab06a321e0 Check if user is enabled before generating auth tokne 2025-10-03 09:34:40 -04:00
Michon van Dooren
240f348f92 Add support for OIDC role claim 2025-09-29 22:27:10 +02:00
Michael Manganiello
e4e3928d1b misc: Apply import sorting 2025-09-04 11:17:00 -03:00