hyperledger-identus / hyperledger-identus/cloud-agent
In-flight deactivation guard not enforced: duplicate deactivate submissions during confirmation window
- Dominant language
- Scala
- Stars
- 95
- Forks
- 34
- Avg merge
- 1d 5h
- Merged PRs (30d)
- 14
Description
Found while analyzing #1883 / #1884.
## Description
The endpoint description of `POST /did-registrar/dids/{didRef}/deactivations` states:
> The deactivation operation is an asynchronous operation and the agent will reject a new deactivate request if the previous operation is not yet confirmed.
This rejection is not implemented for deactivations. A second deactivation submitted while the first is still unconfirmed is accepted (202) and a duplicate signed operation is submitted to the VDR.
## Root cause
`ManagedDIDServiceImpl.deactivateManagedDID` guards with `getUnconfirmedUpdateOperationByDid(...).filterOrFail(_.isEmpty)(MultipleInflightUpdateNotAllowed)` (L236-237), but that store is only populated by `updateManagedDID`, which persists the materialized operation before submission (L201). The deactivation path (`doDeactivate`, L210-215) only signs and submits, persisting nothing, so the guard never sees an in-flight deactivation and is vacuous.
There is a TODO on the method acknowledging this: `// TODO: refactor this method to use the same handler as updateManagedDID`.
Consequence: during the confirmation window the resolved DID is not yet deactivated, all checks pass, and a second `PrismDIDOperation.Deactivate` referencing the same `previousOperationHash` is signed and submitted. One operation confirms and the other is rejected by the VDR as stale. Also note the interlock with #1884: during the window you get a duplicate submission (this issue); after the window you get the 500 instead of 409 (#1884).
## Fix direction
Persist the deactivation operation the same way updates do (share the handler per the TODO), so the in-flight guard sees it. The `MultipleInflightUpdateNotAllowed` 409 it would then raise also needs the endpoint fix tracked in #1884.
## Reproduce
1. Create and publish a DID.
2. `POST .../deactivations`, then immediately `POST .../deactivations` again before the first operation confirms.
3. Observe: second request returns 202 (accepted) instead of the documented rejection.
Contributor guide
Research direction
Start in ManagedDIDServiceImpl.deactivateManagedDID and trace doDeactivate alongside updateManagedDID, especially the store persistence and the TODO about sharing the handler. Reproduce the two immediate POST /did-registrar/dids/{didRef}/deactivations requests, then verify the first deactivation is persisted before submission so the in-flight guard rejects the second request.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- scala
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100