Flow had no ownership concept at all in the schema, so this required adding one:
New nullable flows.owner_id column (migration c7d1f0a92b3e). Pre-existing rows stay NULL, treated as legacy/shared: readable by everyone, but not editable or deletable by anyone via the API (no global admin role exists in this app to grant that safely).
FlowService.create stamps owner_id from the authenticated caller.
FlowService.get_by_id / update / delete now take user_id and raise PermissionDeniedError (→ HTTP 403) on ownership mismatch.
FlowRepository.get_all_with_optional_category filters to owner_id IS NULL OR owner_id = :user_id, fixing the "everyone sees everyone's flows" listing issue.
flows.py routes wire current_user.id through to all of the above and map PermissionDeniedError to 403.
FlowRead schema exposes owner_id so the frontend can gate edit/delete UI.
Test plan
flowsint-core test suite passes (397 passed), including updated test_flow_service_create_uses_timezone_aware_timestamps (now passes owner_id)
flowsint-core/tests/repositories/test_flow_repository.py passes unchanged (new user_id param defaults to None)
Manual verification against a running flowsint dev DB: PUT/DELETE /api/flows/{id} from a non-owner account now returns 403; GET /api/flows no longer returns other users' flows
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.
## 📋 Pull Request Information
**Original PR:** https://github.com/reconurge/flowsint/pull/213
**Author:** [@dextmorgn](https://github.com/dextmorgn)
**Created:** 8/23/2026
**Status:** 🔄 Open
**Base:** `main` ← **Head:** `fix/flow-object-level-authz`
---
### 📝 Commits (1)
- [`dd9caf2`](https://github.com/reconurge/flowsint/commit/dd9caf2cb1861865c71abbe9f45bcc7d3a257bf0) fix(api): enforce object-level authorization on Flow update/delete/read
### 📊 Changes
**7 files changed** (+87 additions, -12 deletions)
<details>
<summary>View changed files</summary>
➕ `flowsint-api/alembic/versions/c7d1f0a92b3e_add_owner_id_to_flows.py` (+45 -0)
📝 `flowsint-api/app/api/routes/flows.py` (+13 -4)
📝 `flowsint-api/app/api/schemas/flow.py` (+1 -0)
📝 `flowsint-core/src/flowsint_core/core/models.py` (+5 -0)
📝 `flowsint-core/src/flowsint_core/core/repositories/flow_repository.py` (+8 -2)
📝 `flowsint-core/src/flowsint_core/core/services/flow_service.py` (+14 -6)
📝 `flowsint-core/tests/services/test_timezone_timestamps.py` (+1 -0)
</details>
### 📄 Description
## Summary
Flow had no ownership concept at all in the schema, so this required adding one:
- New nullable `flows.owner_id` column (migration `c7d1f0a92b3e`). Pre-existing rows stay `NULL`, treated as legacy/shared: readable by everyone, but not editable or deletable by anyone via the API (no global admin role exists in this app to grant that safely).
- `FlowService.create` stamps `owner_id` from the authenticated caller.
- `FlowService.get_by_id` / `update` / `delete` now take `user_id` and raise `PermissionDeniedError` (→ HTTP 403) on ownership mismatch.
- `FlowRepository.get_all_with_optional_category` filters to `owner_id IS NULL OR owner_id = :user_id`, fixing the "everyone sees everyone's flows" listing issue.
- `flows.py` routes wire `current_user.id` through to all of the above and map `PermissionDeniedError` to 403.
- `FlowRead` schema exposes `owner_id` so the frontend can gate edit/delete UI.
## Test plan
- [x] `flowsint-core` test suite passes (397 passed), including updated `test_flow_service_create_uses_timezone_aware_timestamps` (now passes `owner_id`)
- [x] `flowsint-core/tests/repositories/test_flow_repository.py` passes unchanged (new `user_id` param defaults to `None`)
- [ ] Manual verification against a running flowsint dev DB: `PUT`/`DELETE /api/flows/{id}` from a non-owner account now returns 403; `GET /api/flows` no longer returns other users' flows
---
<sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
📋 Pull Request Information
Original PR: https://github.com/reconurge/flowsint/pull/213
Author: @dextmorgn
Created: 8/23/2026
Status: 🔄 Open
Base:
main← Head:fix/flow-object-level-authz📝 Commits (1)
dd9caf2fix(api): enforce object-level authorization on Flow update/delete/read📊 Changes
7 files changed (+87 additions, -12 deletions)
View changed files
➕
flowsint-api/alembic/versions/c7d1f0a92b3e_add_owner_id_to_flows.py(+45 -0)📝
flowsint-api/app/api/routes/flows.py(+13 -4)📝
flowsint-api/app/api/schemas/flow.py(+1 -0)📝
flowsint-core/src/flowsint_core/core/models.py(+5 -0)📝
flowsint-core/src/flowsint_core/core/repositories/flow_repository.py(+8 -2)📝
flowsint-core/src/flowsint_core/core/services/flow_service.py(+14 -6)📝
flowsint-core/tests/services/test_timezone_timestamps.py(+1 -0)📄 Description
Summary
Flow had no ownership concept at all in the schema, so this required adding one:
flows.owner_idcolumn (migrationc7d1f0a92b3e). Pre-existing rows stayNULL, treated as legacy/shared: readable by everyone, but not editable or deletable by anyone via the API (no global admin role exists in this app to grant that safely).FlowService.createstampsowner_idfrom the authenticated caller.FlowService.get_by_id/update/deletenow takeuser_idand raisePermissionDeniedError(→ HTTP 403) on ownership mismatch.FlowRepository.get_all_with_optional_categoryfilters toowner_id IS NULL OR owner_id = :user_id, fixing the "everyone sees everyone's flows" listing issue.flows.pyroutes wirecurrent_user.idthrough to all of the above and mapPermissionDeniedErrorto 403.FlowReadschema exposesowner_idso the frontend can gate edit/delete UI.Test plan
flowsint-coretest suite passes (397 passed), including updatedtest_flow_service_create_uses_timezone_aware_timestamps(now passesowner_id)flowsint-core/tests/repositories/test_flow_repository.pypasses unchanged (newuser_idparam defaults toNone)PUT/DELETE /api/flows/{id}from a non-owner account now returns 403;GET /api/flowsno longer returns other users' flows🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.