Repository navigation
COG-1155: W1 · TransferController recipient delete → POST form (server + Thymeleaf) - #339
Conversation
… uses @GetMapping/@PostMapping Co-Authored-By: patrick.bradley <patrick.bradley@cognition.ai>
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
| } | ||
|
|
||
| @RequestMapping(value = "/recipient/delete", method = RequestMethod.GET) | ||
| @PostMapping("/recipient/delete") |
There was a problem hiding this comment.
🟡 Refreshing recipient page repeats deletion
After a delete POST, refreshing the returned page resubmits recipientDelete. Because deleteRecipientByName uses the name again, a newly added recipient with that name gets deleted.
Learn more
The delete form now submits a POST, but the handler renders recipient as its response. Browsers can resend that POST when refreshing the resulting page. deleteRecipientByName deletes by name rather than by a unique ID, so a second submission can remove a replacement recipient.
Example: A user deletes a recipient named Alex, adds another Alex in a second tab, then refreshes the first tab and confirms resubmission. The new Alex disappears instead of remaining in the recipient list.
Recommended fix: Return redirect:/transfer/recipient after the deletion instead of rendering recipient. The existing GET handler loads the updated list; refreshing its response will only repeat the GET.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Agreed that PRG (redirect:/transfer/recipient) is the better pattern. I'm deliberately leaving it out of this PR: the W1 contract is behaviour-neutral, and the money smoke test in #106 asserts status().isOk() + view().name("recipient") for delete, so switching to a redirect would change that recorded contract. On master, refreshing after a GET delete also repeated the delete. I'm flagging it to the coordinator as a follow-up, which should update the smoke assertion in the same change.
| } | ||
|
|
||
| @RequestMapping(value = "/recipient/delete", method = RequestMethod.GET) | ||
| @PostMapping("/recipient/delete") |
There was a problem hiding this comment.
🟥 Other users' recipients can be deleted
An authenticated user can POST another user's name to recipientDelete and remove that recipient. deleteRecipientByName deletes globally by name; listing recipients afterward does not limit the deletion.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Confirmed, and it is a real problem, but it is not new in this PR. master already had it on the GET mapping: TransactionServiceImpl.deleteRecipientByName → recipientDao.deleteByName deletes by name globally. This happens with findRecipientByName too (edit, and toSomeoneElse transfers). COG-1155 is limited to changing the HTTP method (a W1 behaviour-neutral refactor), and the money smoke suite (#106) records current behaviour, so I have not scoped the service/DAO by owner here. I'm reporting it to the coordinator so it can get its own ticket.
Summary
Linear: COG-1155 · W1 forward-compatible refactor, still on Boot 1.5.4 / Java 8 (no version bump).
Recipient delete was a GET mutation, so once CSRF is enabled (W2) it would still be unprotected (
CsrfFilterskips safe methods). This PR makes it POST now, while CSRF is still disabled. For users nothing changes.TransferController:GET /transfer/recipient/delete?recipientName=…→@PostMapping("/recipient/delete")(under the class-level/transfer), still taking@RequestParam recipientName. The other 7@RequestMapping(value=…, method=…)handlers become@GetMapping/@PostMappingwith the same paths. No trailing-slash variant was added.recipient.html: the<a th:href=".../delete?recipientName=…">link is now a<form th:action="@{/transfer/recipient/delete}" method="post">with a hiddenrecipientNameinput and abtn btn-linksubmit, so it still looks like a link. It usesth:actionso that Spring Security'sCsrfRequestDataValueProcessoradds_csrfautomatically once W2 enables CSRF.Merge order: merge this behind the money smoke suite (#106).
MoneyMovementSmokeTestexists only in #106, not onmaster, so this PR can't edit it without overlapping that PR's files. That test needs this one-line change (get(→post() to pass on top of this PR. It is already anticipated in #106's comment:Whichever PR lands second should apply it (a comment has been posted on #106).
Verification (Temurin 8, MySQL 5.7 in docker)
Build + existing tests:
mvn -B -f UserFront/pom.xml verify -Dspring.datasource.url='jdbc:mysql://localhost:3306/onlinebanking?useSSL=false'(
useSSL=false: connector 5.1.42 can't do a TLS handshake on modern JDK 8. MySQL 8 is incompatible with this connector (query_cache_size), so 5.7 was used, the same image as #106's Testcontainers setup.)Money smoke gate: #106 + this commit + the diff above,
mvn -B -f UserFront/pom.xml verify -Dtest=MoneyMovementSmokeTest:(On this VM's Docker 29, Testcontainers 1.19.7 needed
~/.docker-java.properties→api.version=1.44. That is only a local environment workaround.)Manual flow against the running jar (curl):
(A fresh DB has no
ROLE_USERrow, so signup fails until one is inserted. That problem exists onmasteralready and is not caused by this PR.rolewas seeded by hand for this test.)Devin-Org: engineering
Link to Devin session: https://app.devin.ai/sessions/3ba0f073ef3e4aa3835f31c1c2399cdb
Open in Devin Desktop: https://app.devin.ai/desktop/session/3ba0f073ef3e4aa3835f31c1c2399cdb?variant=devin
Requested by: @patrickbradley-cog
Devin Review