feat: self-serve account deletion#270
Conversation
Locks the design for #224 (beta-blocker): immediate hard delete built on Better Auth deleteUser, with beforeDelete teardown ordered cancel Stripe -> delete D1 subscription row -> wipe DO -> delete identity, so the charged-but-deleted state is unreachable by construction. Ticket stays open as the build ticket. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
More menu → Delete account: type-to-confirm, then an emailed confirmation link whose callback runs the ordered teardown (ADR 0050) — cancel Stripe → delete the D1 subscription row → wipe the outline DO → delete the D1 identity, all in Better Auth's beforeDelete (a throw aborts before the point of no return, so "identity gone but Stripe still charging" is unreachable). - worker/delete-account.ts: teardown + pure guards (resolveDoName, shouldAbortOnStripeCancelError), unit-tested - worker/outline-do.ts: purge() RPC (deleteAll wipes the DO's whole SQLite, then re-migrate to a valid empty schema) - worker/auth.ts: enable user.deleteUser + hooks + confirmation email; email-confirm for everyone (Better Auth routes all deletes through it once the sender is configured — see ADR 0050 amendment) - client: DeleteAccountDialog + More-menu entry + post-delete toast - resolveUserId now delegates to the shared resolveDoName (no drift) Verified end-to-end on cf:dev: dialog gating, email seam, full callback teardown, D1 fully erased (user/session/account/subscription = 0), no errors. Paid-Stripe + Google-token paths remain PR manual-checklist items. Feeds #226 (privacy pages can publish the deletion + 30-day-backup language). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 31 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (11)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Closing in favor of #268, which we're shipping for #224. #268 is the more complete implementation: it deletes the Stripe Customer (not just cancels subs), guards the owner-continuity account against self-delete, has a wider dunning-status sweep, scrubs OAuth rows too, and is e2e-tested including the re-signup no-resurrection check. Two good ideas from this PR (ADR 0050) worth grafting into #268 later if wanted: email-link delete confirmation, and re-migrating the DO to a valid empty schema after deleteAll(). ADR 0051 (#268) already records that it supersedes the 0050 draft. |
Summary
Adds the in-app "delete my account" flow the privacy policy commits to: a confirmed action that cancels any subscription, erases the outline, and deletes the account. Beta-blocking because the legal pages can't publish while they promise a flow that doesn't exist.
Fixes #224
Changes
beforeDelete— cancel Stripe → delete the D1 subscription row → wipe the outline Durable Object → delete the D1 identity — where a throw aborts before the point of no return, so "identity gone but Stripe still charging" is unreachable.deleteUserroute supports, and it's uniform + strongest — recorded as an ADR 0050 amendment.waitlistandinvitestables (best-effort), and the callback redirects the now-signed-out user to a "your account has been deleted" confirmation.purge()RPC (deleteAll()wipes the whole colocated SQLite, then re-migrates to a valid empty schema);resolveUserIdnow shares one DO-name resolver with the delete path so the router and the teardown can't target different DOs.Start here: change 2 — the teardown ordering and the abort-before-identity-delete guarantee.
Flow
Test plan
typecheck,typecheck:worker,typecheck:test,lint,bun test(665),build— all greenworker/delete-account.test.ts(DO-name resolution + Stripe-error abort/no-op classification)cf:devwith a seeded user: dialog gating (button disabled until "DELETE"), email seam fired (correct From/To/Subject + callback URL), clicking the link ran the full teardown, D1 left with user/session/account/subscription all 0, landed signed-out with the confirmation toast, no server errors