Skip to content

Typed API form fields#3036

Merged
gantoine merged 14 commits intomasterfrom
api-form-fields
Mar 7, 2026
Merged

Typed API form fields#3036
gantoine merged 14 commits intomasterfrom
api-form-fields

Conversation

@gantoine
Copy link
Copy Markdown
Member

@gantoine gantoine commented Feb 19, 2026

Description
Explain the changes or enhancements you are proposing with this pull request.

This PR tightens request/response typing across the frontend API layer and refactors several backend endpoints to use typed FastAPI Form/File/Pydantic payloads, while also adding SSRF-focused URL validation for server-side HTTP fetches.

Checklist
Please check all that apply.

  • I've tested the changes locally
  • I've updated relevant comments
  • I've assigned reviewers for this PR
  • I've added unit tests that cover the changes

Screenshots (if applicable)

Screenshot 2026-02-18 at 2 21 14 PM

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @gantoine, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant architectural improvement by standardizing how form data and JSON payloads are handled in the backend API. By moving from manual request parsing to explicit FastAPI dependencies and Pydantic models, the API becomes more robust, type-safe, and self-documenting. The frontend has been comprehensively updated to consume these refined API definitions, ensuring seamless integration and leveraging the benefits of strong typing for API interactions.

Highlights

  • Backend API Refactoring: FastAPI endpoints were refactored to explicitly define form data using Form() and File() dependencies, and JSON request bodies using Pydantic BaseModels. This eliminates manual parsing of request.form() and request.json().
  • Enhanced Type Safety and OpenAPI Generation: The explicit typing of API parameters improves type safety across the application and enhances the accuracy of generated OpenAPI documentation.
  • Frontend API Integration Updates: Frontend API service calls were updated to align with the new backend type definitions, leveraging newly generated TypeScript types and a new buildFormInput utility for streamlined FormData construction.
  • New Utility for FormData Handling: A new utility function, buildFormInput, was introduced in the frontend to simplify the creation of FormData objects for API requests, promoting consistency and reducing boilerplate.
Changelog
  • backend/endpoints/collections.py
    • Modified add_collection, add_smart_collection, update_collection, and update_smart_collection to use File() and Form() for explicit parameter typing.
  • backend/endpoints/configs.py
    • Refactored add_platform_binding, add_platform_version, and add_exclusion to accept Pydantic BaseModel payloads instead of raw JSON.
  • backend/endpoints/rom.py
    • Updated update_rom to use a new RomUpdateForm Pydantic model and update_rom_user to use RomUserUpdatePayload, removing manual FormData parsing.
  • backend/endpoints/saves.py
    • Changed add_save and update_save to use explicit File() parameters for uploaded files.
  • backend/endpoints/screenshots.py
    • Modified add_screenshot to use an explicit File() parameter for the screenshot file.
  • backend/endpoints/states.py
    • Updated add_state and update_state to use explicit File() parameters for state and screenshot files.
  • backend/tests/endpoints/test_config.py
    • Added new test cases to verify the payload shapes for config-related endpoints.
  • backend/tests/endpoints/test_rom.py
    • Added new tests for the update_rom_user endpoint, covering data envelope and last played flags.
  • examples/config.example.yml
    • Updated comments for roms_folder and skip_hash_calculation in the example configuration.
  • frontend/src/generated/index.ts
    • Regenerated OpenAPI types, including new Body_add_*, Body_update_*, ExclusionPayload, PlatformBindingPayload, and RomUserUpdatePayload types.
  • frontend/src/generated/models/Body_add_collection_api_collections_post.ts
    • Modified generated type to include artwork, name, description, and url_cover.
  • frontend/src/generated/models/Body_add_save_api_saves_post.ts
    • Added new generated type for add_save API request body.
  • frontend/src/generated/models/Body_add_screenshot_api_screenshots_post.ts
    • Added new generated type for add_screenshot API request body.
  • frontend/src/generated/models/Body_add_smart_collection_api_collections_smart_post.ts
    • Added new generated type for add_smart_collection API request body.
  • frontend/src/generated/models/Body_add_state_api_states_post.ts
    • Added new generated type for add_state API request body.
  • frontend/src/generated/models/Body_update_collection_api_collections__id__put.ts
    • Modified generated type to include artwork, rom_ids, name, description, and url_cover.
  • frontend/src/generated/models/Body_update_rom_api_roms__id__put.ts
    • Modified generated type to include various metadata IDs, raw metadata fields, and file-related fields.
  • frontend/src/generated/models/Body_update_save_api_saves__id__put.ts
    • Added new generated type for update_save API request body.
  • frontend/src/generated/models/Body_update_smart_collection_api_collections_smart__id__put.ts
    • Added new generated type for update_smart_collection API request body.
  • frontend/src/generated/models/Body_update_state_api_states__id__put.ts
    • Added new generated type for update_state API request body.
  • frontend/src/generated/models/DetailedRomSchema.ts
    • Added ra_hash field to the schema.
  • frontend/src/generated/models/ExclusionPayload.ts
    • Added new generated type for ExclusionPayload.
  • frontend/src/generated/models/PlatformBindingPayload.ts
    • Added new generated type for PlatformBindingPayload.
  • frontend/src/generated/models/RomFileSchema.ts
    • Added ra_hash field to the schema.
  • frontend/src/generated/models/RomUserUpdatePayload.ts
    • Renamed Body_update_rom_user_api_roms__id__props_put.ts to RomUserUpdatePayload.ts and updated its structure.
  • frontend/src/generated/models/SimpleRomSchema.ts
    • Added ra_hash field to the schema.
  • frontend/src/components/common/Collection/Dialog/CreateCollection.vue
    • Adjusted API call to createCollection to remove { data } destructuring.
  • frontend/src/components/common/Collection/Dialog/CreateSmartCollection.vue
    • Adjusted API call to createSmartCollection to remove { data } destructuring.
  • frontend/src/components/common/Game/Dialog/EditRom.vue
    • Adjusted API call to uploadManuals to remove { data } destructuring.
  • frontend/src/components/common/Game/Dialog/UploadRom.vue
    • Adjusted API call to uploadRoms to remove { data } destructuring.
  • frontend/src/components/common/Platform/Dialog/UploadFirmware.vue
    • Adjusted API call to uploadFirmware to remove { data } destructuring.
  • frontend/src/composables/useFavoriteToggle.ts
    • Adjusted API call to createCollection and updated filtering logic for rom_ids.
  • frontend/src/console/views/Play.vue
    • Modified boot function to use buildFormInput for constructing FormData for state and screenshot uploads.
  • frontend/src/services/api/collection.ts
    • Refactored API calls to use buildFormInput for FormData and updated return types to match generated types.
  • frontend/src/services/api/config.ts
    • Updated API calls to use explicit PlatformBindingPayload and ExclusionPayload types for request bodies and adjusted return types.
  • frontend/src/services/api/firmware.ts
    • Updated API calls to use AddFirmwareInput and adjusted return types.
  • frontend/src/services/api/gamelist.ts
    • Adjusted exportGamelist return type.
  • frontend/src/services/api/identity.ts
    • Updated API calls for password reset and logout to use explicit payload types and adjusted return types.
  • frontend/src/services/api/platform.ts
    • Updated API calls for platform management to use explicit payload types and adjusted return types.
  • frontend/src/services/api/rom.ts
    • Extensively refactored API calls for ROM management to use buildFormInput and explicit payload/return types.
  • frontend/src/services/api/save.ts
    • Refactored API calls for save management to use buildFormInput and explicit payload/return types.
  • frontend/src/services/api/screenshot.ts
    • Refactored API calls for screenshot management to use buildFormInput and explicit payload/return types.
  • frontend/src/services/api/setup.ts
    • Updated SetupLibraryInfo to use PlatformSchema.
  • frontend/src/services/api/sgdb.ts
    • Adjusted searchCover return type.
  • frontend/src/services/api/state.ts
    • Refactored API calls for state management to use buildFormInput and explicit payload/return types.
  • frontend/src/services/api/task.ts
    • Adjusted API call return types for task management.
  • frontend/src/services/api/user.ts
    • Updated API calls for user management to use explicit payload types and adjusted return types.
  • frontend/src/utils/formData.ts
    • Added a new utility function buildFormInput to simplify FormData creation.
  • frontend/src/views/Patcher.vue
    • Adjusted API call to uploadPatchedRom to remove { data } destructuring.
Activity
  • The pull request introduces significant changes across both backend and frontend, indicating a coordinated effort to improve API consistency and type safety.
  • Backend endpoints were modified to leverage FastAPI's Form() and File() dependencies, and Pydantic models for request bodies.
  • Frontend code was updated to reflect these backend API changes, including the regeneration of OpenAPI types and the introduction of a new formData utility.
  • New tests were added for config and ROM endpoints to ensure the new payload structures are handled correctly.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 19, 2026

Test Results

820 tests   819 ✅  2m 7s ⏱️
  1 suites    1 💤
  1 files      0 ❌

Results for commit 3fc8c68.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 19, 2026

☂️ Python Coverage

current status: ✅

Overall Coverage

Lines Covered Coverage Threshold Status
13148 8655 66% 0% 🟢

New Files

No new covered files...

Modified Files

File Coverage Status
backend/endpoints/collections.py 30% 🟢
backend/endpoints/configs.py 65% 🟢
backend/endpoints/heartbeat.py 80% 🟢
backend/endpoints/rom.py 50% 🟢
backend/endpoints/saves.py 74% 🟢
backend/endpoints/screenshots.py 40% 🟢
backend/endpoints/states.py 36% 🟢
backend/handler/filesystem/base_handler.py 89% 🟢
backend/handler/filesystem/platforms_handler.py 93% 🟢
backend/handler/filesystem/resources_handler.py 45% 🟢
backend/handler/filesystem/roms_handler.py 79% 🟢
backend/handler/metadata/base_handler.py 94% 🟢
backend/handler/metadata/ra_handler.py 47% 🟢
backend/tasks/manual/cleanup_orphaned_resources.py 34% 🟢
backend/utils/validation.py 93% 🟢
TOTAL 63% 🟢

updated for commit: 3fc8c68 by action🐍

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request significantly refactors the backend API by replacing manual parsing with typed fields using FastAPI's Form and Pydantic models, which enhances type safety, validation, and API documentation. The frontend has been updated accordingly, including a new buildFormInput utility. However, critical security vulnerabilities were identified: Path Traversal due to a lack of sanitization for user-provided filenames in upload endpoints, and Server-Side Request Forgery (SSRF) from insufficient validation of remote URLs used to fetch covers and manuals. These could allow arbitrary file writes or access to internal network resources. It is recommended to apply filename sanitization and URL validation across all affected endpoints. Additionally, an issue was found in the frontend's updateUser API call where multipart/form-data is likely being sent with an incorrect body format.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens request/response typing across the frontend API layer and refactors several backend endpoints to use typed FastAPI Form/File/Pydantic payloads, while also adding SSRF-focused URL validation for server-side HTTP fetches.

Changes:

  • Added a typed buildFormInput helper and migrated several frontend API calls to use OpenAPI-generated body types + typed Axios generics.
  • Refactored backend endpoints (rom/collections/saves/states/screenshots/config) to use structured request parsing and introduced SSRF URL validation.
  • Updated OpenAPI generated frontend types and added/expanded backend tests for new payload shapes and URL validation.

Reviewed changes

Copilot reviewed 36 out of 49 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
frontend/src/views/Patcher.vue Minor TS inference tweak for Promise.allSettled responses.
frontend/src/utils/formData.ts Adds typed FormData builder for safer multipart payload construction.
frontend/src/services/api/user.ts Uses generated request body types and typed Axios responses; adjusts ui_settings serialization.
frontend/src/services/api/task.ts Adds typed Axios generics for task endpoints.
frontend/src/services/api/state.ts Switches to generated schemas and buildFormInput for multipart state uploads/updates.
frontend/src/services/api/sgdb.ts Adds typed Axios generic for cover search response.
frontend/src/services/api/setup.ts Replaces store types with generated schema types.
frontend/src/services/api/screenshot.ts Uses generated schemas and buildFormInput for screenshot uploads/updates.
frontend/src/services/api/save.ts Uses generated schemas and buildFormInput for save uploads/updates; adds typed responses.
frontend/src/services/api/rom.ts Uses generated schemas, typed Axios generics, and buildFormInput for rom update multipart fields; updates user-props payload shape.
frontend/src/services/api/platform.ts Uses generated body types and narrows update payload fields.
frontend/src/services/api/identity.ts Adds generated body types for identity flows and typed Axios generics.
frontend/src/services/api/gamelist.ts Minor signature simplification.
frontend/src/services/api/firmware.ts Types firmware uploads and returns data instead of AxiosResponse.
frontend/src/services/api/config.ts Adds generated payload types and typed Axios generics for config posts.
frontend/src/services/api/collection.ts Uses generated schemas and buildFormInput; returns data for create endpoints; typed responses for others.
frontend/src/console/views/Play.vue Uses buildFormInput for state upload multipart payload (console flow).
frontend/src/composables/useFavoriteToggle.ts Updates to new collectionApi.createCollection return shape and adds explicit id typing in filters.
frontend/src/components/common/Platform/Dialog/UploadFirmware.vue Updates to firmware upload returning data directly.
frontend/src/components/common/Game/Dialog/UploadRom.vue Minor TS inference tweak for Promise.allSettled responses.
frontend/src/components/common/Game/Dialog/EditRom.vue Minor TS inference tweak for Promise.allSettled responses.
frontend/src/components/common/Collection/Dialog/CreateSmartCollection.vue Updates to new createSmartCollection return shape.
frontend/src/components/common/Collection/Dialog/CreateCollection.vue Updates to new createCollection return shape.
frontend/src/generated/models/RomUserUpdatePayload.ts Updates generated model name/shape for rom user props payload.
frontend/src/generated/models/PlatformBindingPayload.ts Adds generated payload model for platform binding posts.
frontend/src/generated/models/ExclusionPayload.ts Adds generated payload model for exclusion posts.
frontend/src/generated/models/Body_update_state_api_states__id__put.ts Adds generated multipart body model for state update.
frontend/src/generated/models/Body_update_smart_collection_api_collections_smart__id__put.ts Adds generated multipart body model for smart collection update.
frontend/src/generated/models/Body_update_save_api_saves__id__put.ts Adds generated multipart body model for save update.
frontend/src/generated/models/Body_update_rom_api_roms__id__put.ts Extends generated multipart body model for rom update fields.
frontend/src/generated/models/Body_update_collection_api_collections__id__put.ts Extends generated multipart body model for collection update fields.
frontend/src/generated/models/Body_add_state_api_states_post.ts Adds generated multipart body model for state upload.
frontend/src/generated/models/Body_add_smart_collection_api_collections_smart_post.ts Adds generated multipart body model for smart collection create.
frontend/src/generated/models/Body_add_screenshot_api_screenshots_post.ts Adds generated multipart body model for screenshot upload.
frontend/src/generated/models/Body_add_save_api_saves_post.ts Adds generated multipart body model for save upload.
frontend/src/generated/models/Body_add_collection_api_collections_post.ts Extends generated multipart body model for collection create fields.
frontend/src/generated/index.ts Re-exports new/renamed generated models.
examples/config.example.yml Clarifies commented filesystem config documentation.
backend/utils/validation.py Adds SSRF-focused URL validation helper.
backend/tests/utils/test_validation.py Adds comprehensive tests for SSRF URL validation behavior.
backend/tests/endpoints/test_rom.py Adds tests for updated rom user-props payload envelope/flags.
backend/tests/endpoints/test_config.py Adds tests validating config endpoint payload shapes.
backend/handler/filesystem/resources_handler.py Validates outbound HTTP fetch URLs (covers/screens/manuals/badges/media) to mitigate SSRF.
backend/endpoints/states.py Refactors state upload/update parsing to typed File params; sanitizes screenshot filename.
backend/endpoints/screenshots.py Refactors screenshot upload parsing to typed File param; sanitizes screenshot filename.
backend/endpoints/saves.py Refactors save upload/update parsing to typed File params; sanitizes screenshot filename.
backend/endpoints/rom.py Refactors rom update to typed form parsing; adds URL validation for cover/manual/screenshots; switches rom user-props endpoint to typed payload.
backend/endpoints/configs.py Refactors config mutation endpoints to typed Pydantic payloads.
backend/endpoints/collections.py Refactors collection endpoints to typed form parsing; adds URL validation for cover URL.
Files not reviewed (13)
  • frontend/src/generated/index.ts: Language not supported
  • frontend/src/generated/models/Body_add_collection_api_collections_post.ts: Language not supported
  • frontend/src/generated/models/Body_add_save_api_saves_post.ts: Language not supported
  • frontend/src/generated/models/Body_add_screenshot_api_screenshots_post.ts: Language not supported
  • frontend/src/generated/models/Body_add_smart_collection_api_collections_smart_post.ts: Language not supported
  • frontend/src/generated/models/Body_add_state_api_states_post.ts: Language not supported
  • frontend/src/generated/models/Body_update_collection_api_collections__id__put.ts: Language not supported
  • frontend/src/generated/models/Body_update_rom_api_roms__id__put.ts: Language not supported
  • frontend/src/generated/models/Body_update_save_api_saves__id__put.ts: Language not supported
  • frontend/src/generated/models/Body_update_smart_collection_api_collections_smart__id__put.ts: Language not supported
  • frontend/src/generated/models/Body_update_state_api_states__id__put.ts: Language not supported
  • frontend/src/generated/models/ExclusionPayload.ts: Language not supported
  • frontend/src/generated/models/PlatformBindingPayload.ts: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 50 changed files in this pull request and generated 12 comments.

Files not reviewed (13)
  • frontend/src/generated/index.ts: Language not supported
  • frontend/src/generated/models/Body_add_collection_api_collections_post.ts: Language not supported
  • frontend/src/generated/models/Body_add_save_api_saves_post.ts: Language not supported
  • frontend/src/generated/models/Body_add_screenshot_api_screenshots_post.ts: Language not supported
  • frontend/src/generated/models/Body_add_smart_collection_api_collections_smart_post.ts: Language not supported
  • frontend/src/generated/models/Body_add_state_api_states_post.ts: Language not supported
  • frontend/src/generated/models/Body_update_collection_api_collections__id__put.ts: Language not supported
  • frontend/src/generated/models/Body_update_rom_api_roms__id__put.ts: Language not supported
  • frontend/src/generated/models/Body_update_save_api_saves__id__put.ts: Language not supported
  • frontend/src/generated/models/Body_update_smart_collection_api_collections_smart__id__put.ts: Language not supported
  • frontend/src/generated/models/Body_update_state_api_states__id__put.ts: Language not supported
  • frontend/src/generated/models/ExclusionPayload.ts: Language not supported
  • frontend/src/generated/models/PlatformBindingPayload.ts: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@gantoine gantoine marked this pull request as ready for review March 7, 2026 14:56
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 7, 2026

Greptile Summary

This PR refactors the frontend API layer to use auto-generated typed models and a buildFormInput utility, and migrates numerous backend endpoints away from manual request.form() / request.json() parsing in favour of typed FastAPI Form(...) / Pydantic body parameters. It also introduces SSRF-prevention URL validation (validate_url_for_http_request) for all server-side HTTP fetches (covers, screenshots, manuals, RA badges), and adds sanitize_filename enforcement for save/state file uploads.

Key points from the review:

  • Orphaned DB record in add_collectiondb_collection_handler.add_collection() is called before the try/except that wraps the SSRF URL check. If the cover URL is blocked, the collection is persisted to the database but the endpoint returns HTTP 400, leaving an orphaned record the user cannot see.
  • shutil.copy2 still blocking in copy_filebase_handler.py correctly converts is_file() and mkdir() to async anyio variants, but the actual file-copy (shutil.copy2) remains a synchronous blocking call that can stall the event loop on large files.
  • Dead entries in RESERVED_HOSTNAMES"[::1]" and "[::]" (with brackets) are unreachable because urlparse already strips brackets from IPv6 literals before returning parsed.hostname.
  • store_ra_badge ValidationError is uncaught — the badge download loop in rom.py and scan.py does not wrap store_ra_badge in a try/except ValidationError, so an unexpected badge URL would surface as a 500 instead of a clean 400.
  • parse_rom_update_form double-parses the form body — the dependency explicitly declares all fields as Form(...) parameters (FastAPI parses the body once), then calls await request.form() again to obtain form_keys. This works because Starlette caches the result, but it couples the dependency to that implementation detail.

Confidence Score: 3/5

  • PR is mostly safe but has a data-consistency bug in add_collection and several previously-flagged issues in the SSRF validation layer that are still unresolved.
  • The frontend typing improvements and backend Form migration are well-structured and low-risk. The SSRF validation adds real security value. Score is held back by: (1) the orphaned-record bug in add_collection introduced by this PR, (2) the still-unaddressed store_ra_badge uncaught ValidationError noted in previous review rounds, and (3) the synchronous shutil.copy2 blocking the event loop in the newly async-ified copy_file.
  • Pay close attention to backend/endpoints/collections.py (orphaned record), backend/handler/filesystem/base_handler.py (blocking copy), and backend/handler/filesystem/resources_handler.py (uncaught ValidationError in badge path).

Important Files Changed

Filename Overview
backend/utils/validation.py New validate_url_for_http_request SSRF-prevention helper; contains dead [::1]/[::] entries in RESERVED_HOSTNAMES and a misleading from e exception chain in the TLD branch.
backend/endpoints/collections.py Migrated form parsing to typed Form(...) params; introduces an orphaned-record bug where add_collection persists the collection to the DB before SSRF URL validation, leaving a dangling record on 400 responses.
backend/endpoints/rom.py Large refactor: new RomUpdateForm/RomUserUpdatePayload Pydantic models and parse_rom_update_form dependency; form body is parsed twice (FastAPI params + await request.form()), coupling the dependency to Starlette's caching behavior.
backend/handler/filesystem/base_handler.py Async-ified is_file() and mkdir() in copy_file, but the downstream shutil.copy2 remains a synchronous blocking call that can stall the event loop for large files.
backend/handler/filesystem/resources_handler.py SSRF validation added to all HTTP resource fetch paths; store_ra_badge calls validate_url_for_http_request but its call-sites in rom.py and scan.py do not catch the resulting ValidationError.
frontend/src/utils/formData.ts New typed buildFormInput utility that skips null/undefined fields and handles Blob vs string values correctly.
frontend/src/services/api/rom.ts Comprehensive migration to generated types; updateRom now uses buildFormInput with a typed fields array; RomUserUpdatePayload and DeleteRomsInput added for JSON endpoints.

Sequence Diagram

sequenceDiagram
    participant FE as Frontend (API service)
    participant EP as FastAPI Endpoint
    participant VL as validate_url_for_http_request
    participant DB as Database Handler
    participant FS as FSResourcesHandler

    Note over FE,FS: updateRom / updateCollection (cover URL path)
    FE->>EP: PUT /roms/{id} (multipart form)
    EP->>EP: parse_rom_update_form (Depends)
    Note right of EP: Calls await request.form() again<br/>(double-parse, cached by Starlette)
    EP->>DB: get_rom(id)
    DB-->>EP: rom
    EP->>FS: get_cover(rom, overwrite, url_cover)
    FS->>VL: validate_url_for_http_request(url_cover)
    alt URL is private/SSRF risk
        VL-->>FS: raises ValidationError
        FS-->>EP: raises ValidationError
        EP->>EP: catch → HTTPException(400)
        EP-->>FE: 400 Bad Request
    else URL is valid
        VL-->>FS: ok
        FS->>FS: httpx.stream(GET, url_cover)
        FS-->>EP: (path_cover_s, path_cover_l)
        EP->>DB: update_rom(cleaned_data)
        DB-->>EP: updated rom
        EP-->>FE: 200 DetailedRomSchema
    end

    Note over FE,FS: add_collection (problematic path)
    FE->>EP: POST /collections (multipart form)
    EP->>DB: add_collection(Collection) ← committed to DB
    EP->>FS: get_cover(collection, overwrite, url_cover)
    FS->>VL: validate_url_for_http_request(url_cover)
    alt URL is private/SSRF risk
        VL-->>FS: raises ValidationError
        FS-->>EP: raises ValidationError
        EP->>EP: catch → HTTPException(400)
        EP-->>FE: 400 Bad Request (but record already in DB!)
    else URL is valid
        VL-->>FS: ok
        FS-->>EP: (path_cover_s, path_cover_l)
        EP->>DB: update collection cover paths
        EP-->>FE: 200 CollectionSchema
    end
Loading

Comments Outside Diff (1)

  1. backend/endpoints/collections.py, line 79-106 (link)

    Orphaned collection record on SSRF validation failure

    db_collection_handler.add_collection(...) is called and committed to the database on line 79 before the try/except block that wraps the cover URL validation. If validate_url_for_http_request raises a ValidationError (e.g. the user supplies a private-IP url_cover), the endpoint returns HTTP 400 — but the collection record already exists in the database with no cover.

    Before this PR the code was safe because get_cover would silently return None on a bad URL rather than raising. The new SSRF checks make the silent failure visible, but introduce this data-consistency gap.

    Move the db_collection_handler.add_collection call inside (or after) the try block, or validate url_cover before persisting, so that a failed validation never leaves an orphaned record:

    # Validate URL before persisting
    if url_cover:
        try:
            validate_url_for_http_request(url_cover, "Cover URL")
        except ValidationError as e:
            raise HTTPException(status_code=400, detail=str(e)) from e
    
    _added_collection = db_collection_handler.add_collection(Collection(**cleaned_data))
    # ... cover fetch (no longer raises ValidationError for scheme/IP issues)

Last reviewed commit: 3fc8c68

@gantoine gantoine merged commit dc2be24 into master Mar 7, 2026
8 of 10 checks passed
@gantoine gantoine deleted the api-form-fields branch March 7, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants