Bug: repeated right-to-be-forgotten request returns 500 with the deleted user's JWT
Summary
After a client successfully deletes its own account with
DELETE /api/v1/users/{username}/right-to-be-forgotten, repeating the same
request with the same JWT returns 500 Internal Server Error.
The first request returns the documented 204. The second request should be a
handled authentication or missing-user response, never a server error.
Environment
- API:
https://awesome.byst.re - Observed: 2026-08-30
- Authentication: JWT issued to a disposable generated
ROLE_CLIENT - Reproducibility: 3/3 independent curl attempts, plus one earlier Playwright fixture-teardown observation
Preconditions
Create a unique disposable client and sign in to obtain its JWT. The successful first deletion removes the generated account, so the sequence leaves no user data that requires separate cleanup.
Steps to reproduce
BASE_URL="https://awesome.byst.re"
USERNAME="forget-repeat-$(uuidgen | tr '[:upper:]' '[:lower:]')"
PASSWORD="ValidPassA1!"
curl --silent --show-error \
--header "Content-Type: application/json" \
--data "{\"username\":\"$USERNAME\",\"email\":\"$USERNAME@example.com\",\"password\":\"$PASSWORD\",\"firstName\":\"RepeatDelete\",\"lastName\":\"Explorer\"}" \
"$BASE_URL/api/v1/users/signup"
TOKEN="$(curl --silent --show-error \
--header "Content-Type: application/json" \
--data "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD\"}" \
"$BASE_URL/api/v1/users/signin" | jq -r '.token')"
curl --include --request DELETE \
--header "Authorization: Bearer $TOKEN" \
"$BASE_URL/api/v1/users/$USERNAME/right-to-be-forgotten"
curl --include --request DELETE \
--header "Authorization: Bearer $TOKEN" \
"$BASE_URL/api/v1/users/$USERNAME/right-to-be-forgotten"
Expected result
- First DELETE:
204 No Content. - Second DELETE: a handled non-5xx response.
401 Unauthorizedis the preferred behavior because the JWT no longer maps to an active principal. If the API intentionally authenticates the orphaned token, the documented missing-user404is an acceptable contract decision.
Actual result
- First DELETE:
204 No Content, empty body, no-store caching. - Second DELETE:
500 Internal Server Error. - Second response content type:
application/json;charset=UTF-8. - Second response body:
{"message":"Internal server error"}
Reproduction evidence
A fresh three-user curl run on 2026-08-30 produced the same status sequence on every attempt:
| Attempt | Signup | Signin | First DELETE | Repeated DELETE |
|---|---|---|---|---|
| 1 | 201 | 200 | 204 | 500 |
| 2 | 201 | 200 | 204 | 500 |
| 3 | 201 | 200 | 204 | 500 |
The L16 Playwright fixture also observed the 500 once during teardown after its
self-delete assertion had already received 204. Later focused runs passed after
teardown was hardened, but the direct curl sequence demonstrates that the backend
defect is deterministic when the request is repeated.
Impact
- A normal retry of a successful destructive request produces an unexpected 5xx.
- Clients cannot distinguish an invalidated identity from a server failure.
- Cleanup and retry logic can report false infrastructure failures after the account was already deleted successfully.
- Server logs receive avoidable internal-error noise for a handled identity lifecycle case.
Contract comparison
api-docs.json documents 204, 401, 403, and 404 for this operation. It
does not document 500.
Likely backend path
The checked backend gives a concrete investigation lead:
JwtTokenProvider.getAuthenticationloads the JWT subject throughMyUserDetails.loadUserByUsername.- After the first deletion, that lookup throws
UsernameNotFoundException. JwtTokenFiltercatches onlyCustomExceptionspecially and maps every other exception to500 Internal server error.
This is an inference from the checked source and should be confirmed against the deployed revision and server stack trace.
Suggested fix and regression coverage
- Map a missing user during JWT authentication to a consistent
401response, or explicitly choose and document404for this operation. - Do not let
UsernameNotFoundExceptionfall into the token filter's generic500branch. - Add a backend regression test that signs in a disposable client, forgets the account, and repeats the DELETE with the original JWT.
- Verify other protected endpoints with a JWT whose user was deleted; they may share the same authentication failure path.
Do not add a Playwright expectation for the 500; the frontend/API regression
suite should keep asserting the intended first-delete behavior while the backend
fix is pending.
