Feature: Shared/private workflows and image boards in multiuser mode - #9018
Conversation
* Add per-user workflow isolation: migration 28, service updates, router ownership checks, is_public endpoint, schema regeneration, frontend UI Co-authored-by: lstein <111189+lstein@users.noreply.github.com> * feat: add shared workflow checkbox to Details panel, auto-tag, gate edit/delete, fix tests Co-authored-by: lstein <111189+lstein@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
…mode (#116) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
…ature/workflow-isolation-in-multiuser-mode
… mode (#120) * Disable Save when editing another user's shared workflow in multiuser mode Co-authored-by: lstein <111189+lstein@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
…-board filter, archive Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
…image, etc.) Previously, images in shared boards owned by another user could not be dragged at all — the draggable setup was completely skipped in GalleryImage.tsx when canWriteImages was false. This blocked ALL drop targets including the viewer, reference image pane, and canvas. Now images are always draggable. The board-move restriction is enforced in the dnd target isValid functions instead: - addImageToBoardDndTarget: rejects moves from shared boards the user doesn't own (unless admin or board is public) - removeImageFromBoardDndTarget: same check Other drop targets (viewer, reference images, canvas, comparison, etc.) remain fully functional for shared board images. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…to copilot/enhancement-allow-shared-boards
JPPhoto
left a comment
There was a problem hiding this comment.
Some issues:
-
Shared-board write protection is still bypassable through direct API calls. The PR only adds read-side visibility checks in boards.py, but the actual mutation routes in board_images.py and images.py still have no
CurrentUserOrDefaultdependency and no owner/visibility checks. In practice, another user can list image names on a shared board via the new allowedGET /boards/{id}/image_names, then callDELETE /images/i/{image_name}orPOST /board_images/to delete/move images anyway. That defeats the PR's core "shared = read-only" guarantee. This needs API-level authorization tests around board-image mutation and image deletion/update, not just UI gating. -
Private workflow thumbnails are still exposed despite the PR summary claiming thumbnail endpoint ownership enforcement. The write endpoints are guarded in the PR, but
GET /api/v1/workflows/i/{workflow_id}/thumbnailin workflows.py still has no user context and no ownership/public check. If a workflow id is known, a private workflow's thumbnail remains fetchable even whenGET /workflows/i/{workflow_id}is now correctly blocked. -
The new
admin_emailfield on unauthenticated setup status leaks administrator identity. The PR extendsGET /api/v1/auth/statusin auth.py to return the first active admin's email, and that route still has no auth requirement. On a public multiuser deployment, this becomes a trivial admin-enumeration/phishing aid. If the frontend needs this for onboarding copy, it should be limited to thesetup_required=truepath or otherwise gated/sanitized.
add_image_to_board and add_images_to_board only checked write access to the destination board, never verifying that the caller owned the source image. An attacker could add a victim's image to their own board, then exploit the board-ownership fallback in _assert_image_owner to gain delete/patch/star/unstar rights on the image. Both endpoints now call _assert_image_direct_owner which requires direct image ownership (image_records.user_id) or admin — board ownership is intentionally not sufficient, preventing the escalation chain. Also fixed a pre-existing bug where HTTPException from the inner loop in add_images_to_board was caught by the outer except-Exception and returned as 500 instead of propagating the correct status code. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Fixed in 8182c08 Fix: private board image enumeration via generic listing endpointsVulnerability
FixBoth Tests (6 new)
|
|
Fixed in 345d039. Fix: privilege escalation via image-to-board reassignmentVulnerability
FixAdded Both Also fixed a pre-existing bug where Tests (2 new, 2 updated)
|
The recall endpoint loaded image files and ran ControlNet preprocessors on any image_name supplied in control_layers or ip_adapters without checking that the caller could read the image. An attacker who knew another user's image UUID could extract dimensions and, for supported preprocessors, mint a derived processed image they could then fetch. Added _assert_recall_image_access() which validates read access for every image referenced in the request before any resolution or processing occurs. Access is granted to the image owner, admins, or when the image sits on a Shared/Public board. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Fixed in 9e7354d. Fix: unauthorized image access via recall parameter resolutionVulnerability
An attacker who knows another user's image UUID (e.g., from a previously-shared board) could exploit both paths. FixAdded
The check runs before the Tests (5 new)
|
list_model_installs, get_model_install_job, pause, resume, restart_failed, and restart_file were unauthenticated — any caller who could reach the API could view sensitive install job fields (source, local_path, error_traceback) and interfere with installation state. All six endpoints now require AdminUserOrDefault, consistent with the neighboring cancel and prune routes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Fixed in a6308b4. Fix: unauthenticated model install job endpointsVulnerabilitySix model install job endpoints were missing authentication, even though the neighboring
Any caller who could reach the API could view sensitive model installation details and interfere with installation state. FixAll six endpoints now require Tests (8 new)
|
I'm willing to live with the current level of partial redaction. I work on large academic compute clusters, and I'm used to the queue manager listing user names, what nodes their tasks are executing on, and the name of the task. Labs can be pretty competitive with each other, but nobody seems to mind this level of information leakage. I think this will only become a problem for InvokeAI when it is mounted as a hosted solution, in which case users might not want their competitors looking over their shoulders. For the time being, I'd like to present enough information that a user can see how many jobs are in the queue in front of theirs and possibly complain to the admin when someone is hogging the system. |
There's still a problem with bulk downloads. The chain is:
So the code path is still:
Hope this analysis helps. |
|
Plus...
Public-board write semantics are still not implemented on the backend. The PR description says public boards should allow all users to read, write, and delete contained images, but the router checks still require owner or admin. In board_images.py, |
|
Feel free to apply this git patch to expose the flaws. |
…w findings
Bulk download capability token exfiltration:
- Socket events now route to user:{user_id} + admin rooms instead of the
shared 'default' room (the earlier toast race that blocked this approach
was fixed in a prior commit).
- GET /download/{name} re-requires CurrentUserOrDefault and enforces
ownership via get_owner().
- Frontend download handler replaced <a download> (which cannot carry auth
headers) with fetch() + Authorization header + programmatic blob download.
Additional fixes from reviewer tests:
- Public boards now grant write access in _assert_board_write_access and
mutation rights in _assert_image_owner (BoardVisibility.Public).
- Uncategorized image listing (GET /boards/none/image_names) now filters
to the caller's images only, preventing cross-user enumeration.
- board_images router uses board_image_records.get_board_for_image()
instead of images.get_dto() to avoid dependency on image_files service.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6baa510 to
58cb8aa
Compare
My mistake. I was trying to fix a race condition that prevented the "downloads are ready" toast from popping up and made the private websocket room public while debugging. Issues addressed in 58cb8aa Fix: close bulk download exfiltration pathBulk download capability token exfiltrationBulk download flow still leaks the zip filename (capability token) to other users:
Fixes (3 layers):
Additional fixes from @JPPhoto 's tests (thanks!)Public board write access (
Uncategorized image enumeration (
board_images router resilience (
|
Defense-in-depth: the route layer already checks ownership before calling update/delete/update_is_public/update_opened_at, but the SQL statements did not include AND user_id = ?, so a bypass of the route check would allow cross-user mutations. All four methods now accept an optional user_id parameter. When provided, the SQL WHERE clause is scoped to that user. The route layer passes current_user.user_id for non-admin callers and None for admins. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Public-board write semantics are still incomplete for uploads. In |
upload_image() blocked non-owner uploads even to public boards. The board write check now allows uploads when board_visibility is Public, consistent with the public-board semantics in _assert_board_write_access and _assert_image_owner. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixed in c19f040 |
|
I think this is all good. I'll try testing it out manually today. |
JPPhoto
left a comment
There was a problem hiding this comment.
I was able to do some minimal testing on a live system here and it looks good. Would love to see an announcement and more community testing of this feature after it gets merged.
PR #9018 assigned preexisting workflows user_id='system' with is_public=FALSE, causing them to disappear from all users' libraries. - Add migration 30: sets is_public=TRUE for all system-owned workflows so they appear under "Shared Workflows" - Skip user_id filtering for admins in workflow listing endpoints so they can see and manage all workflows including system-owned ones - Show the Shared toggle to admins on system-owned workflows in the workflow list UI - Update multiuser user guide to document private/shared workflows, private/shared/public image boards, and warn about preexisting workflows appearing in shared section - Add regression tests for system workflow visibility, admin CRUD access, and regular user access denial Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…r non-admins (#9262) * fix(multiuser): restore global queue counts + redacted entries for non-admins PR #9018 changed the queue status/list endpoints from "global counts + per-user counts" to "user-scoped only" for non-admins. This silently broke two multiuser behaviors: 1. The hamburger-menu badge lost its "X/Y" form. #9018 dropped the user_pending/user_in_progress fields, made pending/in_progress/total user-scoped (so a non-admin had no global total), and reduced the badge to a single number. 2. The virtualized queue list stopped showing other users' redacted entries. #9018 added `AND user_id = ?` to get_queue_item_ids, so a non-admin only ever received their own item ids — the (intact) sanitize_queue_item_for_user redaction never ran for other users' items. Restore the original design (PR #8822) while keeping the #9087 current-item identifier redaction: - SessionQueueStatus regains optional user_pending/user_in_progress. - get_queue_status always computes GLOBAL aggregate counts and, when user_id is provided, additionally returns that user's own counts. The current item's identifiers are still gated to the owner/admin via a single get_current() snapshot (race-free, #9087 preserved). - get_queue_item_ids no longer filters by user; ids carry no sensitive data and items are redacted at hydration by sanitize_queue_item_for_user, so non-admins again see partially-redacted entries for other users' jobs. - QueueCountBadge renders "<own>/<global total>" in multiuser mode and the plain total for admins / single-user mode. - Regenerate the frontend OpenAPI schema. Aggregate counts are global but non-identifying (no session_id/batch_id/ params/graph), consistent with #9087's "counts remain global" decision. Tests: - Update the model-shape test to assert user_pending/user_in_progress exist. - Add session-queue integration tests: global counts + per-user subcounts, admin/global omission of subcounts, current-item redaction with global counts intact, and get_queue_item_ids returning every user's ids. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(frontend): typegen + openapi * chore(backend): ruff --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
…r non-admins (invoke-ai#9262) * fix(multiuser): restore global queue counts + redacted entries for non-admins PR invoke-ai#9018 changed the queue status/list endpoints from "global counts + per-user counts" to "user-scoped only" for non-admins. This silently broke two multiuser behaviors: 1. The hamburger-menu badge lost its "X/Y" form. invoke-ai#9018 dropped the user_pending/user_in_progress fields, made pending/in_progress/total user-scoped (so a non-admin had no global total), and reduced the badge to a single number. 2. The virtualized queue list stopped showing other users' redacted entries. invoke-ai#9018 added `AND user_id = ?` to get_queue_item_ids, so a non-admin only ever received their own item ids — the (intact) sanitize_queue_item_for_user redaction never ran for other users' items. Restore the original design (PR invoke-ai#8822) while keeping the invoke-ai#9087 current-item identifier redaction: - SessionQueueStatus regains optional user_pending/user_in_progress. - get_queue_status always computes GLOBAL aggregate counts and, when user_id is provided, additionally returns that user's own counts. The current item's identifiers are still gated to the owner/admin via a single get_current() snapshot (race-free, invoke-ai#9087 preserved). - get_queue_item_ids no longer filters by user; ids carry no sensitive data and items are redacted at hydration by sanitize_queue_item_for_user, so non-admins again see partially-redacted entries for other users' jobs. - QueueCountBadge renders "<own>/<global total>" in multiuser mode and the plain total for admins / single-user mode. - Regenerate the frontend OpenAPI schema. Aggregate counts are global but non-identifying (no session_id/batch_id/ params/graph), consistent with invoke-ai#9087's "counts remain global" decision. Tests: - Update the model-shape test to assert user_pending/user_in_progress exist. - Add session-queue integration tests: global counts + per-user subcounts, admin/global omission of subcounts, current-item redaction with global counts intact, and get_queue_item_ids returning every user's ids. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(frontend): typegen + openapi * chore(backend): ruff --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
* feat: Per-user workflow libraries in multiuser mode (#114) * Add per-user workflow isolation: migration 28, service updates, router ownership checks, is_public endpoint, schema regeneration, frontend UI Co-authored-by: lstein <111189+lstein@users.noreply.github.com> * feat: add shared workflow checkbox to Details panel, auto-tag, gate edit/delete, fix tests Co-authored-by: lstein <111189+lstein@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lstein <111189+lstein@users.noreply.github.com> * Restrict model sync to admin users only (#118) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lstein <111189+lstein@users.noreply.github.com> * feat: distinct splash screens for admin/non-admin users in multiuser mode (#116) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lstein <111189+lstein@users.noreply.github.com> * Disable Save when editing another user's shared workflow in multiuser mode (#120) * Disable Save when editing another user's shared workflow in multiuser mode Co-authored-by: lstein <111189+lstein@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: lstein <111189+lstein@users.noreply.github.com> * chore(app): ruff * Add board visibility (private/shared/public) feature with tests and UI Co-authored-by: lstein <111189+lstein@users.noreply.github.com> * Enforce read-only access for non-owners of shared/public boards in UI Co-authored-by: lstein <111189+lstein@users.noreply.github.com> * Fix remaining board access enforcement: invoke icon, drag-out, change-board filter, archive Co-authored-by: lstein <111189+lstein@users.noreply.github.com> * fix: allow drag from shared boards to non-board targets (viewer, ref image, etc.) Previously, images in shared boards owned by another user could not be dragged at all — the draggable setup was completely skipped in GalleryImage.tsx when canWriteImages was false. This blocked ALL drop targets including the viewer, reference image pane, and canvas. Now images are always draggable. The board-move restriction is enforced in the dnd target isValid functions instead: - addImageToBoardDndTarget: rejects moves from shared boards the user doesn't own (unless admin or board is public) - removeImageFromBoardDndTarget: same check Other drop targets (viewer, reference images, canvas, comparison, etc.) remain fully functional for shared board images. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(security): add auth requirement to all sensitive routes in multimodal mode * chore(backend): ruff * Add workflow live update events Stacked on top of origin PR #9018 (shared/private workflows and boards) for multiuser workflow visibility semantics. * Add persisted call-saved-workflows node stub * Add saved workflow picker state handling * Clarify saved workflow missing state * Validate selected saved workflows at runtime * Generalize saved workflow picker into a field type * Harden saved workflow field behavior * Rename saved workflow node to singular * Fix saved workflow frontend build issues * Add dynamic saved workflow form fields * Seed and handle dynamic workflow fields * Align dynamic workflow fields with node styling * Use standard templates for dynamic workflow fields * Revert stale dynamic edge graph guard * Preserve inbound edges when compatbile with a new selected workflow * Add call saved workflow design document * Clarify workflow return runtime design * Add workflow return node contract * Allow bounded recursive workflow calls * fix (backend): improve user isolation for session queue and recall parameters - Sanitize session queue information of all cross-user fields except for the timestamps and status. - Recall parameters are now user-scoped. - Queue status endpoints now report user-scoped activity rather than global activity - Tests added: TestSessionQueueSanitization (4 tests): 1. test_owner_sees_all_fields - Owner sees complete queue item data 2. test_admin_sees_all_fields - Admin sees complete queue item data 3. test_non_owner_sees_only_status_timestamps_errors - Non-owner sees only item_id, queue_id, status, and timestamps; everything else is redacted 4. test_sanitization_does_not_mutate_original - Sanitization doesn't modify the original object TestRecallParametersIsolation (2 tests): 5. test_user1_write_does_not_leak_to_user2 - User1's recall params are not visible in user2's client state 6. test_two_users_independent_state - Both users can write recall params independently without overwriting each other fix(backend): queue status endpoints report user-scoped stats rather than global stats * fix(workflow): do not filter default workflows in multiuser mode Problem: When categories=['user', 'default'] (or no category filter) and user_id was set for multiuser scoping, the SQL query became WHERE category IN ('user', 'default') AND user_id = ?, which excluded default workflows (owned by "system"). Fix: Changed user_id = ? to (user_id = ? OR category = 'default') in all 6 occurrences across workflow_records_sqlite.py — in get_many, counts_by_category, counts_by_tag, and get_all_tags. Default workflows are now always visible regardless of user scoping. Tests added (2): - test_default_workflows_visible_when_listing_user_and_default — categories=['user','default'] includes both - test_default_workflows_visible_when_no_category_filter — no filter still shows defaults * Fix frontend lint on workflow node fixtures * Add workflow call runtime state scaffolding * Add workflow call runner boundary handling * Create child workflow execution state on call boundary * Update workflow call runtime documentation * Localize saved workflow node UI strings * Execute saved workflow children inline * chore: ruff * Test inline saved workflow execution semantics * Fix saved workflow lint and schema warnings * Updated documentation * Propagate workflow return values to callers * Fix lazy If branch pruning and skipped-parent handling in graph runtime * Extract workflow call coordinator * Track workflow call lifecycle state * Add workflow call queue item metadata * Add workflow call queue item migration * Queue child workflow executions and suspend parents * Cascade workflow call cancel and root retry * Refine workflow call queue UI and docs * Harden workflow call queue API behavior * Harden workflow call retry event delivery * Extract workflow call runtime module * Format workflow call queue migration * Validate callable workflow compatibility * Support direct batched child workflows * Support generator-backed batched child workflows * Split workflow call runtime tests * Clarify workflow call batch semantics * Expose callable workflow compatibility metadata * Surface callable workflow compatibility in library UI * Fix workflow call privacy and compatibility edge cases * Harden workflow call queue boundary and cleanup semantics * Harden workflow call cancel retry and batch edge cases * Repaired database migration * bring documentation back in line with the tested current state * Harden saved workflow frontend compatibility state * Treat waiting queue items as nonterminal in staging area * chore: typegen * Harden workflow call child propagation * Tighten workflow call compatibility reporting * Removed to-do items that have been done * Update docs * Documented `WorkflowCallQueueLifecycle` as an intentional bounded component * Fix saved workflow selection fallback * Fix workflow call startup cancellation * Fix single-user workflow socket rooms * Ignore stale saved workflow input payloads * Fix saved workflow picker discovery * Preserve workflow call batch field values * chore: linting * Harden workflow-call queue cancellation semantics * Harden saved workflow caller UI state * Avoid eager workflow compatibility expansion * Fix workflow call README list nesting * chore: mdformat * Updated documentation * Removed the anonymous collection from the workflow return production contract, fixed various bugs. * chore: lint * Remove noise in tests * Bugfixes and optimization - workflow_return can now take a single or collection * Bugfixes * Fixed a persistence failure * Fix saved workflow call field rehydration * Preserve workflow call completion state on resume * Update queue status from socket events * Updated tests to match the current workflow-return contract * Fix workflow child queue status isolation * chore: typegen * chore: typegen * Fixed failing migration test * Updated documentation to match schema * Update call saved workflow design docs * Renamed to follow current convention * chore: typegen * chore: openapi.json * Fix tests * chore: typegen * chore: lint * Fix workflow socket test async backend * Test workflow call batch cancellation * Address outstanding issues based on PR comments * Test workflow call destination cancellation Add the missing F2 regression test for destination-scoped bulk cancellation of workflow-call parent/child chains. * Fix workflow call runtime test double * chore: typegen/openapi * chore: typegen/openapi * Fixed database migration 33 * Update tests after queue status merge * chore: lint --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: lstein <111189+lstein@users.noreply.github.com> Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
Summary
This PR gives users the ability to share workflows and image boards when logged in to a multiuser instance.
Workflow Sharing
User can share workflows with each other as well as create private workflows that are invisible to other users. Each user has their own workflow library that is only accessible to them. Any workflow can be designated Shared, in which case it is readable by all users but only writable by the owner.
Backend
user_id(DEFAULT'system') andis_public(DEFAULTFALSE) columns + indexes toworkflow_library. Usesexecutescript()for the DDL statements so that transaction management is handled explicitly (executescript always issues COMMIT first), which avoids edge-cases in Python'ssqlite3implicit transaction handling for DDL on tables that contain VIRTUAL generated columns. A post-check raises a clearRuntimeErrorif the columns were not actually added, preventing silent failures. No cross-module imports in the migration callback.WORKFLOW_LIBRARY_DEFAULT_USER_IDconstant added toworkflow_records_common.pyto avoid magic strings across service and base layersworkflow_records_*: All query methods (create,get_many,counts_by_*,get_all_tags) acceptuser_idandis_publicfilters; newupdate_is_public()method — which also automatically adds the"shared"tag whenis_public=trueand removes it whenis_public=falseworkflows.pyrouter:CurrentUserOrDefaultlist_workflows/counts_by_*/get_all_tagsautomatically scopeusercategory results to the current user in multiuser mode (bypassed whenis_public=trueis explicitly requested)GET(non-owner blocked unless public/default/admin),PATCH,DELETE, and thumbnail endpointsPATCH /api/v1/workflows/i/{workflow_id}/is_publicendpointFrontend
openapi.jsonupdated;schema.tsregenerated viamake frontend-typegenWorkflowRecordOrderBygainsis_public;WorkflowLibraryViewgains'shared'WorkflowLibrarySideNav;WorkflowListroutes'shared'view tois_public=truequeryWorkflowGeneralTab(Details panel): newShareWorkflowCheckboxcomponent positioned between the workflow Name field and the Workflow Thumbnail section — visible to the workflow owner and admins in multiuser mode; label and checkbox are on the same horizontal line; togglesis_publicvia the APIWorkflowListItem: owners see aShareWorkflowToggleswitch; public workflows display a "Shared" badge;EditWorkflowandDeleteWorkflowbuttons are now gated behindisOwner || is_adminso non-owners cannot edit or delete others' workflowsSaveWorkflowAsDialog: "Share workflow" checkbox — marks new workflow public immediately after creationWorkflowSortControl+ sort options updated to includeis_publicsharedWorkflows,shareWorkflow("Shared workflow") added toen.jsonSingle-user mode behavior is completely unchanged — no workflow filtering is applied when
multiuserisfalse.QA Instructions
** Warning: ** be aware that this PR performs a database migration before testing.
Workflow Isolation
multiuser: trueand create two users: user1 and user2. If the user interface for managing users isn't yet merged in, use theinvoke-useraddCLI.user1 workflow sharedanduser1 workflow privateBoard Sharing
This PR implements image board sharing among users when multiuser mode is active. It adds three visibility levels for the boards:
Summary
Backend
BoardVisibilityenum (private|shared|public) added toboard_records_common.py;board_visibilityfield added toBoardRecordandBoardChangesboard_visibility TEXT NOT NULL DEFAULT 'private'column toboardstable; migrates existingis_public=1rows to'public'board_records_sqlite.py:update()handlesboard_visibility;get_many()/get_all()queries useboard_visibility IN ('shared', 'public')instead ofis_public = 1boards.pyrouter:get_boardandlist_all_board_image_namesallow non-owner access for shared/public boards;update_boardanddelete_boardremain owner/admin-onlyFrontend
schema.ts:BoardVisibilityenum,board_visibilityfield onBoardDTOandBoardChangesBoardContextMenu.tsx: "Set Private / Set Shared / Set Public" menu items (owner and admins only); visibility handlers extracted into nameduseCallbackhooks to comply withreact/jsx-no-bind; Delete Board, Archive, and Unarchive menu items are disabled (greyed out) for non-owners of shared and public boardsBoardEditableTitle.tsx: pencil icon and double-click rename hidden for non-owners of shared and public boardsGalleryBoard.tsx: blue share icon badge for shared boards, green globe badge for public boards; DnD drop target disabled for non-owners of shared boardsGalleryImage.tsx: drag-out disabled for non-owners viewing a shared board, preventing images from being moved outGalleryItemDeleteIconButton.tsx: shift+hover trash icon hidden when viewing a shared board as a non-ownerContextMenuItemDeleteImage.tsx: delete image menu item hidden when viewing a shared board as a non-ownerContextMenuItemChangeBoard.tsx: "Change Board" menu item disabled when viewing a shared board as a non-ownerMultipleSelectionMenuItems.tsx: "Change Board" and "Delete Selection" disabled when viewing a shared board as a non-ownerInvokeQueueBackButton.tsx: main Invoke/generate button disabled when the auto-add board is a shared board the current user does not ownFloatingLeftPanelButtons.tsx: floating invoke icon button also disabled when the auto-add board is a shared board the current user does not ownChangeBoardModal.tsx: destination board list filtered to exclude shared boards the current user does not own, preventing moves into read-only boardsuseBoardAccess(board)returns{ canWriteImages, canRenameBoard, canDeleteBoard };useSelectedBoard()anduseAutoAddBoard()look up the relevantBoardDTOfrom the RTK Query cacheen.json: i18n strings for all new visibility UITests
10 new tests in
test_boards_multiuser.pycovering default visibility, setting each level, cross-user access enforcement, reversion to private, non-owner restriction, and admin override.QA Instructions
** Warning: ** be aware that this PR performs a database migration before testing.
multiuser: truein config)Additional Fixes in this PR
There are two small UI bugs that were found and fixed while working on this PR.
The splash screens that the user sees at startup time have been customized such that non-admin users are not prompted to install models. Instead they are referred to the first administrator to install models and make other global settings.
This PR also corrects a bug that allowed any user to run "sync models" and remove orphaned models. Only the administrator is allowed to do this.
QA
Splash Screens
To test, you should start with a fresh root directory that has no models.
Sync Models button
Confirm that as a non-admin user, the Models page does not show the "Sync Models" button, and that this button reappears when logged in as an administrator.
Merge Plan
Migrations 28 and 29 add new columns to the boards and workflows tables with safe defaults so existing databases upgrade non-destructively. No redux slice changes.
Checklist
What's Newcopy (if doing a release after this PR)