diff options
author | 2023-03-31 15:01:29 +0200 | |
---|---|---|
committer | 2023-03-31 15:01:29 +0200 | |
commit | d9bbcc60a6cd32282de907a2090c674c4616219e (patch) | |
tree | c007cf145ebe24fcc3de420020722a4ea70fb803 /internal/processing/account | |
parent | [bugfix] Fix relationship not updating 'following' on accept follow request (... (diff) | |
download | gotosocial-d9bbcc60a6cd32282de907a2090c674c4616219e.tar.xz |
[bugfix] Fix report serialization errors caused by user delete (#1659)
* [bugfix] Fix report serialization errors caused by user delete
* fix tests
Diffstat (limited to 'internal/processing/account')
-rw-r--r-- | internal/processing/account/delete.go | 70 | ||||
-rw-r--r-- | internal/processing/account/delete_test.go | 102 |
2 files changed, 170 insertions, 2 deletions
diff --git a/internal/processing/account/delete.go b/internal/processing/account/delete.go index f3dfecc7b..2a20ec96e 100644 --- a/internal/processing/account/delete.go +++ b/internal/processing/account/delete.go @@ -21,15 +21,18 @@ import ( "context" "errors" "fmt" + "net" "time" "codeberg.org/gruf/go-kv" + "github.com/google/uuid" "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/messages" + "golang.org/x/crypto/bcrypt" ) const deleteSelectLimit = 50 @@ -136,8 +139,13 @@ func (p *Processor) deleteUserAndTokensForAccount(ctx context.Context, account * } } - if err := p.state.DB.DeleteUserByID(ctx, user.ID); err != nil { - return fmt.Errorf("deleteUserAndTokensForAccount: db error deleting user: %w", err) + columns, err := stubbifyUser(user) + if err != nil { + return fmt.Errorf("deleteUserAndTokensForAccount: error stubbifying user: %w", err) + } + + if err := p.state.DB.UpdateUser(ctx, user, columns...); err != nil { + return fmt.Errorf("deleteUserAndTokensForAccount: db error updating user: %w", err) } return nil @@ -473,3 +481,61 @@ func stubbifyAccount(account *gtsmodel.Account, origin string) []string { "enable_rss", } } + +// stubbifyUser renders the given user as a stub, +// removing sensitive information like IP addresses +// and sign-in times, but keeping email addresses to +// prevent the same email address from creating another +// account on this instance. +// +// `encrypted_password` is set to the bcrypt hash of a +// random uuid, so if the action is reversed, the user +// will have to reset their password via email. +// +// For caller's convenience, this function returns the db +// names of all columns that are updated by it. +func stubbifyUser(user *gtsmodel.User) ([]string, error) { + uuid, err := uuid.New().MarshalBinary() + if err != nil { + return nil, err + } + + dummyPassword, err := bcrypt.GenerateFromPassword(uuid, bcrypt.DefaultCost) + if err != nil { + return nil, err + } + + var never = time.Time{} + + user.EncryptedPassword = string(dummyPassword) + user.SignUpIP = net.IPv4zero + user.CurrentSignInAt = never + user.CurrentSignInIP = net.IPv4zero + user.LastSignInAt = never + user.LastSignInIP = net.IPv4zero + user.SignInCount = 1 + user.Locale = "" + user.CreatedByApplicationID = "" + user.LastEmailedAt = never + user.ConfirmationToken = "" + user.ConfirmationSentAt = never + user.ResetPasswordToken = "" + user.ResetPasswordSentAt = never + + return []string{ + "encrypted_password", + "sign_up_ip", + "current_sign_in_at", + "current_sign_in_ip", + "last_sign_in_at", + "last_sign_in_ip", + "sign_in_count", + "locale", + "created_by_application_id", + "last_emailed_at", + "confirmation_token", + "confirmation_sent_at", + "reset_password_token", + "reset_password_sent_at", + }, nil +} diff --git a/internal/processing/account/delete_test.go b/internal/processing/account/delete_test.go new file mode 100644 index 000000000..5a68eda0c --- /dev/null +++ b/internal/processing/account/delete_test.go @@ -0,0 +1,102 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +package account_test + +import ( + "context" + "net" + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +type AccountDeleteTestSuite struct { + AccountStandardTestSuite +} + +func (suite *AccountDeleteTestSuite) TestAccountDeleteLocal() { + ctx := context.Background() + + // Keep a reference around to the original account + // and user, before the delete was processed. + ogAccount := suite.testAccounts["local_account_1"] + ogUser := suite.testUsers["local_account_1"] + + testAccount := >smodel.Account{} + *testAccount = *ogAccount + + suspensionOrigin := "01GWVP2A8J38Q2J2FDZ6TS8AQG" + if err := suite.accountProcessor.Delete(ctx, testAccount, suspensionOrigin); err != nil { + suite.FailNow(err.Error()) + } + + updatedAccount, err := suite.db.GetAccountByID(ctx, ogAccount.ID) + if err != nil { + suite.FailNow(err.Error()) + } + + suite.WithinDuration(time.Now(), updatedAccount.UpdatedAt, 1*time.Minute) + suite.Zero(updatedAccount.FetchedAt) + suite.Zero(updatedAccount.AvatarMediaAttachmentID) + suite.Zero(updatedAccount.AvatarRemoteURL) + suite.Zero(updatedAccount.HeaderMediaAttachmentID) + suite.Zero(updatedAccount.HeaderRemoteURL) + suite.Zero(updatedAccount.DisplayName) + suite.Nil(updatedAccount.EmojiIDs) + suite.Nil(updatedAccount.Emojis) + suite.Nil(updatedAccount.Fields) + suite.Zero(updatedAccount.Note) + suite.Zero(updatedAccount.NoteRaw) + suite.False(*updatedAccount.Memorial) + suite.Zero(updatedAccount.AlsoKnownAs) + suite.Zero(updatedAccount.Reason) + suite.False(*updatedAccount.Discoverable) + suite.Zero(updatedAccount.StatusContentType) + suite.Zero(updatedAccount.CustomCSS) + suite.WithinDuration(time.Now(), updatedAccount.SuspendedAt, 1*time.Minute) + suite.Equal(suspensionOrigin, updatedAccount.SuspensionOrigin) + suite.True(*updatedAccount.HideCollections) + suite.False(*updatedAccount.EnableRSS) + + updatedUser, err := suite.db.GetUserByAccountID(ctx, testAccount.ID) + if err != nil { + suite.FailNow(err.Error()) + } + + suite.WithinDuration(time.Now(), updatedUser.UpdatedAt, 1*time.Minute) + suite.NotEqual(updatedUser.EncryptedPassword, ogUser.EncryptedPassword) + suite.Equal(net.IPv4zero, updatedUser.SignUpIP) + suite.Zero(updatedUser.CurrentSignInAt) + suite.Equal(net.IPv4zero, updatedUser.CurrentSignInIP) + suite.Zero(updatedUser.LastSignInAt) + suite.Equal(net.IPv4zero, updatedUser.LastSignInIP) + suite.Equal(1, updatedUser.SignInCount) + suite.Zero(updatedUser.Locale) + suite.Zero(updatedUser.CreatedByApplicationID) + suite.Zero(updatedUser.LastEmailedAt) + suite.Zero(updatedUser.ConfirmationToken) + suite.Zero(updatedUser.ConfirmationSentAt) + suite.Zero(updatedUser.ResetPasswordToken) + suite.Zero(updatedUser.ResetPasswordSentAt) +} + +func TestAccountDeleteTestSuite(t *testing.T) { + suite.Run(t, new(AccountDeleteTestSuite)) +} |