3e72b88839
* harden(api): enforce auth on 4 routers, fix 4 router bugs, add route tests Add auth dependencies to download_queue, style_presets, model_relationships, and utilities routers, which previously had none — under multiuser=True these endpoints were anonymously reachable. CurrentUserOrDefault for read/per-resource routes; AdminUserOrDefault for global actions (prune/cancel-all downloads, style preset import/export, model relationship add/remove). Single-user mode behavior is unchanged. Fix four bugs in the same routers: - model_relationships: HTTPException(400) from the self-relationship check was caught by `except Exception` and re-raised as 500. Move the check before the try-block and drop the broad except so the 400 reaches the client. - style_presets update_style_preset: image was persisted before json.loads could fail, leaving the preset image partially updated on bad payloads. Validate `data` first, then mutate image state. - style_presets create/update: json.JSONDecodeError was not caught alongside pydantic.ValidationError; malformed JSON surfaced as 500. Catch both → 400. - download_queue download(): `Path(dest)` with no validation. Reject absolute paths, '..' segments, and empty strings with 400 before the service is called. Add shared multiuser test fixtures in tests/app/routers/conftest.py and new route-level tests for download_queue, style_presets, model_relationships, utilities, and virtual_boards covering anonymous-rejection, role gating, and the four bug regressions. Enable --cov=invokeai in pytest addopts so the existing fail_under = 85 threshold is actually enforced. * Ruff * chore: openapi.json * harden(api): enforce auth on 4 routers, fix 4 router bugs, scope style presets per-user, add route tests Add auth dependencies to download_queue, style_presets, model_relationships, and utilities routers, which previously had none — under multiuser=True these endpoints were anonymously reachable. CurrentUserOrDefault for read/per-resource routes; AdminUserOrDefault for global actions (prune/cancel-all downloads, style preset import/export, model relationship add/remove). Single-user mode behavior is unchanged. Fix four bugs in the same routers: - model_relationships: HTTPException(400) from the self-relationship check was caught by `except Exception` and re-raised as 500. Move the check before the try-block and drop the broad except so the 400 reaches the client. - style_presets update_style_preset: image was persisted before json.loads could fail, leaving the preset image partially updated on bad payloads. Validate `data` first, then mutate image state. - style_presets create/update: json.JSONDecodeError was not caught alongside pydantic.ValidationError; malformed JSON surfaced as 500. Catch both -> 400. - download_queue download(): `Path(dest)` with no validation. Reject absolute paths, '..' segments, and empty strings with 400 before the service is called. Scope style presets per-user. Migration 27 added `user_id` and `is_public` columns months ago, but the service layer never read or wrote them — every authenticated user saw and could mutate every preset. This commit wires them up: - StylePresetRecordDTO gains `user_id`; StylePresetWithoutId / StylePresetChanges / StylePresetFormData gain `is_public`. - SqliteStylePresetRecordsStorage.create / create_many / _sync_default_style_presets write user_id (system for defaults). get_many takes user_id + is_admin and filters SQL to: admin sees all, otherwise own ∪ default ∪ public. - style_presets router gains _assert_preset_read / _assert_preset_write helpers mirroring _assert_image_owner in routers/images.py. Get/update/delete/image load the record first, then enforce: 403 for non-owner on private presets, 403 for non-admin attempting to mutate a Default preset, 403 for non-admin trying to create a Default preset. Add shared multiuser test fixtures in tests/app/routers/conftest.py: enable_multiuser patches ApiDependencies across the relevant router modules and swaps None-valued services for MagicMock, plus wires a real SqliteStylePresetRecordsStorage on the in-memory DB so cross-user SQL filtering is actually exercised. Add route-level tests for download_queue, style_presets, model_relationships, utilities, and virtual_boards covering anonymous-rejection, role gating, the four bug regressions, and the full ownership matrix for style presets (own/public/default/admin) on get, list, update, delete, image fetch, and is_public flip. Rename tests/app/routers/test_download_queue.py to test_download_queue_router.py to avoid a pytest collection collision with the long-standing tests/app/services/download/test_download_queue.py. Enable --cov=invokeai in pytest addopts so the existing fail_under = 85 threshold is actually enforced. * chore(pytest): drop --cov=invokeai from this PR Reviewer pointed out that current full-suite coverage is ~48% while fail_under is set to 85, so activating --cov=invokeai on this PR would trip CI the moment it lands. Tests added in this PR remain (they cover the bug regressions and auth gating). Coverage-gate activation will be bundled into a follow-up PR together with whatever additional tests are needed to clear the threshold. * refactor(api): share access helpers, tighten conftest, polish review nits Address remaining feedback from the code review (issues 9200#issuecomment-4480805221): - Extract _assert_image_owner / _assert_image_read_access / _assert_board_read_access from routers/images.py into a new routers/_access.py module (functions exported without the underscore prefix). routers/images.py keeps local _assert_* aliases so its 13 internal call sites stay untouched. routers/utilities.py imports the helper at module top instead of reaching into routers/images.py privates from inside image_to_prompt. - Reorder current_user to come before the body parameter in expand_prompt and image_to_prompt, matching the convention used by the rest of the routers in this PR and by routers/images.py. - Simplify _validate_dest: '..' in posix.parts or '..' in windows.parts is more direct than the set-union allocation. - Harden tests/app/routers/conftest.py: pull the eight monkeypatched module paths into a _PATCHED_API_DEPENDENCIES_MODULES tuple so adding a new router is one list entry instead of one more setattr call. Add two WARNING blocks to the module docstring — one calling out the unconditional MagicMock replacements (style_preset_records is the one real-SQLite exception), and one reminding future authors that new routers must be appended to the tuple. - test_multiuser_authorization.py: its local enable_multiuser fixture now also patches routers/_access.ApiDependencies, since the image read-access check used by its image-mutation tests now resolves names in _access's globals. * chore: openapi.json and typegen * Chore Ruff --------- Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com> Co-authored-by: JPPhoto <jpollack@jpollackphoto.com> Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>