From c6791c3c586b614691f6703d0992af0db2e0513e Mon Sep 17 00:00:00 2001 From: Knight Date: Sat, 18 Jul 2026 13:08:36 +0530 Subject: [PATCH] fix: clean up orphaned user-keyed tables in deleteUser (#38511) ### Description This PR addresses orphaned user-keyed tables during user deletion by adding the missing models to the `db.DeleteBeans` call in `services/user/delete.go`: - `auth_model.TwoFactor` (TOTP secrets) - `auth_model.WebAuthnCredential` (WebAuthn keys) - `activities_model.Notification` (Notifications) - `issues_model.IssueWatch` (Issue watches) Additionally, it adds corresponding unit test coverage in `services/user/user_test.go` to assert that these records are cleaned up successfully. ### Related Issues Fixes #38510 Signed-off-by: pranav718 --- services/user/delete.go | 4 ++++ services/user/user_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/services/user/delete.go b/services/user/delete.go index 5592ac7c4b1..e6ff7075c44 100644 --- a/services/user/delete.go +++ b/services/user/delete.go @@ -96,6 +96,10 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error) &user_model.Blocking{BlockeeID: u.ID}, &actions_model.ActionRunnerToken{OwnerID: u.ID}, &actions_model.ActionScopedWorkflowSource{OwnerID: u.ID}, + &auth_model.TwoFactor{UID: u.ID}, + &auth_model.WebAuthnCredential{UserID: u.ID}, + &activities_model.Notification{UserID: u.ID}, + &issues_model.IssueWatch{UserID: u.ID}, ); err != nil { return fmt.Errorf("deleteBeans: %w", err) } diff --git a/services/user/user_test.go b/services/user/user_test.go index a20694400f0..3daf57639f9 100644 --- a/services/user/user_test.go +++ b/services/user/user_test.go @@ -9,8 +9,10 @@ import ( "testing" "time" + activities_model "gitea.dev/models/activities" "gitea.dev/models/auth" "gitea.dev/models/db" + issues_model "gitea.dev/models/issues" "gitea.dev/models/organization" repo_model "gitea.dev/models/repo" "gitea.dev/models/unittest" @@ -61,6 +63,32 @@ func TestDeleteUser(t *testing.T) { org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) assert.Error(t, DeleteUser(t.Context(), org, false)) + + t.Run("CleanupOrphanedTables", func(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // assert they exist before deletion + unittest.AssertExistsAndLoadBean(t, &auth.TwoFactor{UID: 24}) + unittest.AssertExistsAndLoadBean(t, &auth.WebAuthnCredential{UserID: 32}) + unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{UserID: 2}) + unittest.AssertExistsAndLoadBean(t, &issues_model.IssueWatch{UserID: 2}) + + // delete users + user24 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 24}) + assert.NoError(t, DeleteUser(t.Context(), user24, true)) + + user32 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 32}) + assert.NoError(t, DeleteUser(t.Context(), user32, true)) + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + assert.NoError(t, DeleteUser(t.Context(), user2, true)) + + // assert they do not exist after deletion + unittest.AssertNotExistsBean(t, &auth.TwoFactor{UID: 24}) + unittest.AssertNotExistsBean(t, &auth.WebAuthnCredential{UserID: 32}) + unittest.AssertNotExistsBean(t, &activities_model.Notification{UserID: 2}) + unittest.AssertNotExistsBean(t, &issues_model.IssueWatch{UserID: 2}) + }) } func TestDeleteUserUnlinkedAttachments(t *testing.T) {