feat(account): cascade account delete to connected apps (BUNYIP-211) #231

Merged
Claude-Run merged 1 commit from feat/bunyip-211-cascade-account-delete into main 2026-06-26 01:50:11 +02:00
Member

Deleting a bunyip account now fans an account_deleted webhook out to every active application with a webhook_url (mokosh and any other connected PSA app), so a "deleted" bunyip account no longer leaves a fully-populated downstream tenant behind.

Bunyip side of BUNYIP-211:

  • Add BunyipEvent::AccountDeleted { user_id } to the in-process bus: name() returns account_deleted, target_user() returns the user. Published after the soft delete commits so local subscribers (audit tail, SSE) see the terminal event.
  • Add WebhookService::notify_account_deleted (fire-and-forget) plus dispatch_account_deleted, which retries the signed delivery 3 times with exponential backoff and returns the outcome. Sign-once over a fixed payload keeps the body and X-Webhook-Signature HMAC identical across retries.
  • delete_account fans the webhook out (in a spawned task so a slow/unreachable downstream cannot block the already-committed delete), records a per-app audit row for every dispatch outcome, and persists a replayable row in the new account_delete_dispatch_failures table when an app exhausts its retries.
  • Add admin-gated POST /v1/admin/account-deletes/{user_id}/replay to re-fire a single app's webhook for a stuck delete, recording the outcome the same way the fan-out does.
  • bunyip-web delete confirmation copy now warns that connected-app data is purged; the existing two-step (password + TOTP) confirm is preserved.

Tested: wiremock integration tests assert the receiver sees account_deleted with the correct user_id and a valid HMAC, that the dispatch retries to exhaustion and reports the error, and that a no-webhook app is a no-op success. fmt + clippy (-D warnings) + workspace lib tests green.

The mokosh-server receiver (POST /v1/webhooks/bunyip/account-deleted, sections 5-7 of the issue) is companion work in a separate repo and remains to be filed as a MAPPS ticket.

#BUNYIP-211

Deleting a bunyip account now fans an `account_deleted` webhook out to every active application with a `webhook_url` (mokosh and any other connected PSA app), so a "deleted" bunyip account no longer leaves a fully-populated downstream tenant behind. Bunyip side of BUNYIP-211: - Add `BunyipEvent::AccountDeleted { user_id }` to the in-process bus: `name()` returns `account_deleted`, `target_user()` returns the user. Published after the soft delete commits so local subscribers (audit tail, SSE) see the terminal event. - Add `WebhookService::notify_account_deleted` (fire-and-forget) plus `dispatch_account_deleted`, which retries the signed delivery 3 times with exponential backoff and returns the outcome. Sign-once over a fixed payload keeps the body and `X-Webhook-Signature` HMAC identical across retries. - `delete_account` fans the webhook out (in a spawned task so a slow/unreachable downstream cannot block the already-committed delete), records a per-app audit row for every dispatch outcome, and persists a replayable row in the new `account_delete_dispatch_failures` table when an app exhausts its retries. - Add admin-gated `POST /v1/admin/account-deletes/{user_id}/replay` to re-fire a single app's webhook for a stuck delete, recording the outcome the same way the fan-out does. - bunyip-web delete confirmation copy now warns that connected-app data is purged; the existing two-step (password + TOTP) confirm is preserved. Tested: wiremock integration tests assert the receiver sees `account_deleted` with the correct `user_id` and a valid HMAC, that the dispatch retries to exhaustion and reports the error, and that a no-webhook app is a no-op success. fmt + clippy (-D warnings) + workspace lib tests green. The mokosh-server receiver (`POST /v1/webhooks/bunyip/account-deleted`, sections 5-7 of the issue) is companion work in a separate repo and remains to be filed as a MAPPS ticket. #BUNYIP-211
feat(account): cascade account delete to connected apps (BUNYIP-211)
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 1m11s
Check / fmt + clippy + build + tests (pull_request) Successful in 8m1s
Create release / Create release from merged PR (pull_request) Has been skipped
2589b1dbcd
Deleting a bunyip account now fans an `account_deleted` webhook out to every active application with a `webhook_url` (mokosh and any other connected PSA app), so a "deleted" bunyip account no longer leaves a fully-populated downstream tenant behind.

Bunyip side of BUNYIP-211:

- Add `BunyipEvent::AccountDeleted { user_id }` to the in-process bus: `name()` returns `account_deleted`, `target_user()` returns the user. Published after the soft delete commits so local subscribers (audit tail, SSE) see the terminal event.
- Add `WebhookService::notify_account_deleted` (fire-and-forget) plus `dispatch_account_deleted`, which retries the signed delivery 3 times with exponential backoff and returns the outcome. Sign-once over a fixed payload keeps the body and `X-Webhook-Signature` HMAC identical across retries.
- `delete_account` fans the webhook out (in a spawned task so a slow/unreachable downstream cannot block the already-committed delete), records a per-app audit row for every dispatch outcome, and persists a replayable row in the new `account_delete_dispatch_failures` table when an app exhausts its retries.
- Add admin-gated `POST /v1/admin/account-deletes/{user_id}/replay` to re-fire a single app's webhook for a stuck delete, recording the outcome the same way the fan-out does.
- bunyip-web delete confirmation copy now warns that connected-app data is purged; the existing two-step (password + TOTP) confirm is preserved.

Tested: wiremock integration tests assert the receiver sees `account_deleted` with the correct `user_id` and a valid HMAC, that the dispatch retries to exhaustion and reports the error, and that a no-webhook app is a no-op success. fmt + clippy (-D warnings) + workspace lib tests green.

The mokosh-server receiver (`POST /v1/webhooks/bunyip/account-deleted`, sections 5-7 of the issue) is companion work in a separate repo and remains to be filed as a MAPPS ticket.

#BUNYIP-211
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/bunyip!231
No description provided.