Repository navigation
Conversation
DeleteUser removed only the user row. The user's identity mappings kept
resolving to a GUID with no user, so bootstrap and LDAP import treated a
deleted username as existing, and app self-registration answered 409.
- DeleteUser now removes the mappings the user still owns, its global
roles and its direct permissions, in the same transaction (Bolt and
Postgres). DeleteApp's owner-scoped loop is now the shared helper
deleteOwnedMappings.
- Both delete handlers clear an app-local user's role assignment and
revoke tokens and sessions. DELETE /api/app/users/{guid} did not
revoke them before.
- On open, both stores log mappings that point at deleted users. They
remove them only with SA_REPAIR_DANGLING_MAPPINGS=1, because removing
an old ldap/kerberos mapping lets that deleted user sign in again.
Behaviour change: a deleted directory user can now sign in again and is
provisioned a new account. Disable the account to keep them out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
DeleteUserremoved only the user row, in both stores. The user's identity mappings, global roles and directpermissions stayed, so
local:<username>kept resolving to a GUID with no user:POST /api/admin/bootstrapansweredcreated: falsefor a deleted username. It set roles on a GUID with no userand never created the account again. We hit this re-bootstrapping a deployment after deleting a test user.
status: exists).DELETE /api/admin/users/{guid}. M14 fixed onlyDELETE /api/app/users/{guid}and noted the admin path as afollow-up. The admin path also kept the app's role assignment, which is keyed by username, so a user created again
under that name inherited the old roles.
Found while fixing it:
DELETE /api/app/users/{guid}never revoked the deleted user's tokens or SSO sessions. Theadmin delete did. A token issued before the delete kept working until it expired.
Fix
DeleteUserdeletes, in the same transaction as the user row, the identity mappings the user still owns, itsglobal roles and its direct permissions.
DeleteApp(H8 / H14)is now a shared helper,
deleteOwnedMappings;DeleteAppbehaves exactly as before.user_guid/guid.Both delete handlers share two helpers:
deleteLocalUserclears an app-local user's role assignment in its app. It reads everything before deleting, andreturns a 500 with a clear message if the assignment cannot be saved, instead of ignoring the failure.
purgeDeletedUserStaterevokes tokens and sessions. It moved out ofhandleDeleteUser, so the app path nowrevokes them too.
Both handlers take
localUserMu, so an app-local create of the same username cannot run between the delete andthe cleanup.
On open, both stores log every mapping that already points at a deleted user (
provider=… external_id=…).They leave these in place by default and remove them only with
SA_REPAIR_DANGLING_MAPPINGS=1.can still match the name (assignment keys are shared with directory users).
SA_SKIP_MAPPING_REPAIR=1overrides the opt-in.TestPostgresConnectionskips the check.Docs:
SECURITY-AUDIT.md: Audit Pass 6 / M41 entry and Status Summary row.docs/API.md: what the two delete routes remove.docs/CONFIGURATION.md: the two startup switches.Behaviour change
Before this change, deleting an LDAP/Kerberos user also kept them out by accident. The leftover
ldap:mappingresolved to the deleted GUID and the login failed. Now the mapping goes with the user, so the user's next login
provisions a new account. That account picks up any app role assignments still kept under their username or
sAMAccountName, and their group assignments. To keep a directory user out, disable the account in SimpleAuth
(
PUT /api/admin/users/{guid}/disabled) or in the directory (H17).That is also why the startup check only reports by default. Removing old dangling
ldap/kerberosmappings wouldlet users deleted before this change sign in again. Operators can review the log, then:
DELETE /api/admin/users/{guid}/mappings/{provider}/{external_id}(this does notclear app role assignments), or
SA_REPAIR_DANGLING_MAPPINGS=1.No API shape changes; the SDKs are unaffected. A restored Bolt backup is checked on the next start.
Tests
internal/store/delete_user_test.go(Bolt) covers:internal/store/postgres_delete_user_test.gocovers the same for Postgres, plusTestPostgresConnectionnotwriting. These run when
SA_TEST_POSTGRES_DSNis set, are skipped otherwise, and clean up their rows.internal/handler/delete_user_m41_test.gocovers:These tests fail on
master:userinfoanswers 404 insteadof 401.
These describe behaviour
masteralready has and guard against regressions:go build ./...,go vet ./internal/...,gofmtandgo test ./...pass.go test ./...ran with the Postgrestests enabled, against Postgres 16.
🤖 Generated with Claude Code