-
Notifications
You must be signed in to change notification settings - Fork 292
fix(api): validate transaction category, merchant and tag ownership #2823
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
4912ada
84f1df8
751799a
fdb4178
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -96,6 +96,8 @@ def create | |
| return render_existing_idempotent_entry(existing_entry) | ||
| end | ||
|
|
||
| return unless validate_family_associations | ||
|
|
||
| @entry = account.entries.new(entry_params_for_create) | ||
|
|
||
| if @entry.save | ||
|
|
@@ -140,6 +142,8 @@ def update | |
| return | ||
| end | ||
|
|
||
| return unless validate_family_associations | ||
|
|
||
| Entry.transaction do | ||
| if @entry.update(entry_params_for_update) | ||
| # Handle tags separately - only when explicitly provided in the request | ||
|
|
@@ -318,6 +322,40 @@ def account_id_param | |
| params.dig(:transaction, :account_id).presence | ||
| end | ||
|
|
||
| # Category, merchant and tag references are assigned straight onto the | ||
| # record, so an unusable id has to be rejected here rather than reaching the | ||
| # database. A malformed UUID casts to NULL and the association is dropped | ||
| # while the request still reports success, an unknown UUID trips the foreign | ||
| # key and surfaces as a 500, and a UUID owned by another family is accepted | ||
| # outright. Returns false once a response has been rendered. | ||
| def validate_family_associations | ||
| family = current_resource_owner.family | ||
|
|
||
| category_id = transaction_params[:category_id] | ||
| if category_id.present? && !family_owns?(family.categories, category_id) | ||
| render_validation_error("Category not found or does not belong to your family") | ||
| return false | ||
| end | ||
|
|
||
| merchant_id = transaction_params[:merchant_id] | ||
| if merchant_id.present? && !family_owns?(family.merchants, merchant_id) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a family has provider-synced transactions, Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed and fixed in 84f1df8 — thank you, this was a real regression. Verified against the dev container: a Rather than duplicate the index's scope, I extracted it into |
||
| render_validation_error("Merchant not found or does not belong to your family") | ||
| return false | ||
| end | ||
|
|
||
| tag_ids = Array(transaction_params[:tag_ids]).reject(&:blank?).uniq | ||
| if tag_ids.any? && !tag_ids.all? { |id| family_owns?(family.tags, id) } | ||
| render_validation_error("One or more tags were not found or do not belong to your family") | ||
| return false | ||
| end | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| true | ||
| end | ||
|
|
||
| def family_owns?(scope, id) | ||
| valid_uuid?(id) && scope.exists?(id: id) | ||
| end | ||
|
|
||
| def entry_params_for_create | ||
| entry_params = { | ||
| name: transaction_params[:name] || transaction_params[:description], | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When PATCH supplies an invalid or foreign category, merchant, or tag, this new guard returns 422, but the PATCH operation in
spec/requests/api/v1/transactions_spec.rbstill documents only 200/404 responses and does not describe the ownership constraints; the generated OpenAPI YAML mirrors that omission. Update the rswag PATCH operation and regenerate the docs so clients receive the newly introduced contract.AGENTS.md reference: AGENTS.md:L39-L46
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 84f1df8. The PATCH operation in
spec/requests/api/v1/transactions_spec.rbnow documents the family-ownership requirement oncategory_id,merchant_idandtag_ids(keeping the existing omit-vs-[]tag semantics) and adds the 422 response, anddocs/api/openapi.yamlis regenerated.One note on the regenerated file:
rswag:specs:swaggerizealso emits pre-existing drift unrelated to this PR (the merchants CSV import endpoint,MerchantImportResult, and the transfer fee fields), from specs already onmainthat were never regenerated. I left that out to keep this diff reviewable — it is still outstanding onmainand probably worth a separate sync.