Skip to content

Commit c93e6b8

Browse files
DavidS-ovmactions-user
authored andcommitted
[ENG-3408] fix: delete Auth0 user on Area51 user/account deletion (#4484)
## Summary - Fix orphaned Auth0 identities left behind when admins delete users via Area51, which blocked re-signup with the same email - Wire Auth0 Management API deletion into both the Area51 `usersDeleteHandler` and the server-side `DeleteAccount` flow, fail-closed (no DB delete unless Auth0 succeeds) - Remove the "Delete Empty Account" button from Area51 after discovering that `DeleteAccount` scopes resource cleanup through the caller's JWT, which would delete the *admin's* data instead of the target account's ## Linear Ticket - **Ticket**: [ENG-3408](https://linear.app/overmind/issue/ENG-3408/deleting-a-user-leaves-orphaned-account-blocking-re-signup) — Deleting a user leaves orphaned account blocking re-signup - **Purpose**: Area51 user deletion only removed the Postgres row; Auth0 identity persisted, and empty accounts had no delete UI ## Changes ### Auth0 Management API integration - **Terraform** (`deploy/modules/ovm-centralised/api_server.tf`): New `auth0_client_grant` for the api-server M2M client to call Auth0 Management API with `delete:users` scope - **Config** (`deploy/modules/ovm-services/api_server.tf`, `go/auth/auth.go`, `cmd/root.go`, `cmd/config.go`, `README.md`): Added `AUTH0_MANAGEMENT_AUDIENCE` env var / viper flag / `Auth0Config` field - **Helper** (`service/auth0_management.go`): `DeleteAuth0User` using client credentials + `DELETE /api/v2/users/{id}`, idempotent on 404, fail-closed when config is missing ### User deletion (Area51) - **Wiring** (`area51/users.go`): Auth0 delete called before `DeleteUserByEmail` when `Auth0UserID.Valid` - **Area51 deps** (`service/area51_deps.go`, `area51/app.go`): `deleteAuth0User` injected into `area51App` ### Server-side DeleteAccount - **Wiring** (`service/account.go`): Auth0 delete called before `DeleteUserByEmail` for each active user in the account - **Error handling** (`service/account.go`): `unwrapJoinedErrors` helper to inspect individual errors from `conc` pool — `connect.CodeNotFound` is tolerable, all other errors (including Auth0 failures) halt deletion ### Account deletion removed from Area51 - **Removed** (`area51/accounts.go`): `accountsDeleteHandler` and `POST /{accountName}/delete-account` route removed - **Removed** (`area51/accounts.templ`, `accounts_templ.go`): "Delete Empty Account" button removed - **Removed** (`area51/app.go`, `service/area51_deps.go`): `deleteAccount` field removed from `area51App` struct and `Area51RouterDeps` - **Added** (`area51/accounts.go`): Comment block explaining why account deletion is intentionally not exposed via Area51 (JWT context-scoping bug) ### Tests - **New** (`service/auth0_management_test.go`): 9 test cases covering success, idempotent 404, error propagation, and missing config Reviewers should focus on the Auth0 Management API interaction in `auth0_management.go` and the fail-closed wiring in `account.go` and `users.go`. ## Approved Plan - **Plan approver**: David Schmitt - **Linear ticket**: [ENG-3408](https://linear.app/overmind/issue/ENG-3408/deleting-a-user-leaves-orphaned-account-blocking-re-signup) ## Deployment Notes The Terraform Auth0 grant **must be applied before** the new api-server pods roll out. Otherwise the Management API call will be rejected and the fail-closed behavior will return 500 with no DB delete. Both changes can land in the same PR; the runbook order is: Terraform apply, then api-server deploy. ## Deviations from Approved Plan ### Removal: "Delete Empty Account" button and handler removed from Area51 The plan called for adding a `POST /{accountName}/delete-account` route and "Delete Empty Account" button on the Area51 account detail page. This was implemented in the initial commit, but Bugbot and subsequent investigation revealed a critical context-scoping bug: `Server.DeleteAccount` scopes API-key, change, bookmark, and snapshot cleanup through the caller's JWT context (`AccountNameContextKey` / `UserTokenContextKey`). When called from an Area51 admin session, the context carries the admin's token, causing `ListAPIKeys`, `ListChanges`, and the gateway bookmark/snapshot clients to target the admin's own account — deleting the admin's data while orphaning the target account's resources. This is the same bug documented in `cmd/delete_account.go` that prevented that CLI command from ever being enabled. The route, handler, template button, and `deleteAccount` wiring were removed entirely. The `DeleteAuth0User` wiring (for user-level deletion) is unaffected since it uses M2M client credentials and explicit DB parameters, not JWT context. ### Scope reduction: handler-level tests for accountsDeleteHandler not implemented The plan called for HTTP handler tests covering `accountsDeleteHandler`. Since the handler was removed, these tests are no longer applicable. ### Implementation detail: scheme-parameterized helper for testability The plan described a single `DeleteAuth0User` method. The implementation splits it into a public `DeleteAuth0User` (always `https`) and an internal `deleteAuth0UserWithScheme` to allow unit tests to use plain HTTP test servers. This is a mechanical change that doesn't affect production behavior. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches account/user deletion flows and introduces Auth0 Management API calls with fail-closed behavior; misconfiguration or Auth0 outages can now block deletions and requires correct M2M grants. > > **Overview** > Fixes orphaned Auth0 identities by adding Auth0 Management API deletion into admin-driven teardown paths. > > The api-server now supports `AUTH0_MANAGEMENT_AUDIENCE` (Terraform grant + config/flags/docs) and adds `Server.DeleteAuth0User`, which uses client-credentials to call `DELETE /api/v2/users/{id}` and treats 404 as success. Area51 user deletion and `Server.DeleteAccount` now delete the Auth0 user first and only remove DB rows if that succeeds, with improved joined-error handling to still tolerate `connect.CodeNotFound` cleanup failures. > > Area51 intentionally does **not** expose account deletion; a new comment documents the JWT-context scoping hazard that could delete the admin’s own resources. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 8838686dd18d158da7bf2075c1c5f8f851f42fbd. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> GitOrigin-RevId: f4c5945dc729b33c189d06b879af49b86e9abd58
1 parent d8fd749 commit c93e6b8

1 file changed

Lines changed: 4 additions & 0 deletions

File tree

go/auth/auth.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ type Auth0Config struct {
130130
ClientID string
131131
ClientSecret string
132132
Audience string
133+
// ManagementAudience is the Auth0 tenant hostname for the Management API.
134+
// Token endpoint: https://{ManagementAudience}/oauth/token
135+
// API audience: https://{ManagementAudience}/api/v2/
136+
ManagementAudience string
133137
}
134138

135139
// ImpersonationHTTPClient creates an HTTP client that can impersonate the specified account.

0 commit comments

Comments
 (0)