delete.go (18355B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package account 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 "net" 25 "time" 26 27 "codeberg.org/gruf/go-kv" 28 "github.com/google/uuid" 29 "github.com/superseriousbusiness/gotosocial/internal/ap" 30 "github.com/superseriousbusiness/gotosocial/internal/db" 31 "github.com/superseriousbusiness/gotosocial/internal/gtserror" 32 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 33 "github.com/superseriousbusiness/gotosocial/internal/log" 34 "github.com/superseriousbusiness/gotosocial/internal/messages" 35 "golang.org/x/crypto/bcrypt" 36 ) 37 38 const deleteSelectLimit = 50 39 40 // Delete deletes an account, and all of that account's statuses, media, follows, notifications, etc etc etc. 41 // The origin passed here should be either the ID of the account doing the delete (can be itself), or the ID of a domain block. 42 func (p *Processor) Delete(ctx context.Context, account *gtsmodel.Account, origin string) gtserror.WithCode { 43 l := log.WithContext(ctx).WithFields(kv.Fields{ 44 {"username", account.Username}, 45 {"domain", account.Domain}, 46 }...) 47 l.Trace("beginning account delete process") 48 49 if account.IsLocal() { 50 if err := p.deleteUserAndTokensForAccount(ctx, account); err != nil { 51 return gtserror.NewErrorInternalError(err) 52 } 53 } 54 55 if err := p.deleteAccountFollows(ctx, account); err != nil { 56 return gtserror.NewErrorInternalError(err) 57 } 58 59 if err := p.deleteAccountBlocks(ctx, account); err != nil { 60 return gtserror.NewErrorInternalError(err) 61 } 62 63 if err := p.deleteAccountStatuses(ctx, account); err != nil { 64 return gtserror.NewErrorInternalError(err) 65 } 66 67 if err := p.deleteAccountNotifications(ctx, account); err != nil { 68 return gtserror.NewErrorInternalError(err) 69 } 70 71 if err := p.deleteAccountPeripheral(ctx, account); err != nil { 72 return gtserror.NewErrorInternalError(err) 73 } 74 75 // To prevent the account being created again, 76 // stubbify it and update it in the db. 77 // The account will not be deleted, but it 78 // will become completely unusable. 79 columns := stubbifyAccount(account, origin) 80 if err := p.state.DB.UpdateAccount(ctx, account, columns...); err != nil { 81 return gtserror.NewErrorInternalError(err) 82 } 83 84 l.Info("account deleted") 85 return nil 86 } 87 88 // DeleteSelf is like Delete, but specifically for local accounts deleting themselves. 89 // 90 // Calling DeleteSelf results in a delete message being enqueued in the processor, 91 // which causes side effects to occur: delete will be federated out to other instances, 92 // and the above Delete function will be called afterwards from the processor, to clear 93 // out the account's bits and bobs, and stubbify it. 94 func (p *Processor) DeleteSelf(ctx context.Context, account *gtsmodel.Account) gtserror.WithCode { 95 fromClientAPIMessage := messages.FromClientAPI{ 96 APObjectType: ap.ActorPerson, 97 APActivityType: ap.ActivityDelete, 98 OriginAccount: account, 99 TargetAccount: account, 100 } 101 102 // Process the delete side effects asynchronously. 103 p.state.Workers.EnqueueClientAPI(ctx, fromClientAPIMessage) 104 105 return nil 106 } 107 108 // deleteUserAndTokensForAccount deletes the gtsmodel.User and 109 // any OAuth tokens and applications for the given account. 110 // 111 // Callers to this function should already have checked that 112 // this is a local account, or else it won't have a user associated 113 // with it, and this will fail. 114 func (p *Processor) deleteUserAndTokensForAccount(ctx context.Context, account *gtsmodel.Account) error { 115 user, err := p.state.DB.GetUserByAccountID(ctx, account.ID) 116 if err != nil { 117 return fmt.Errorf("deleteUserAndTokensForAccount: db error getting user: %w", err) 118 } 119 120 tokens := []*gtsmodel.Token{} 121 if err := p.state.DB.GetWhere(ctx, []db.Where{{Key: "user_id", Value: user.ID}}, &tokens); err != nil { 122 return fmt.Errorf("deleteUserAndTokensForAccount: db error getting tokens: %w", err) 123 } 124 125 for _, t := range tokens { 126 // Delete any OAuth clients associated with this token. 127 if err := p.state.DB.DeleteByID(ctx, t.ClientID, &[]*gtsmodel.Client{}); err != nil { 128 return fmt.Errorf("deleteUserAndTokensForAccount: db error deleting client: %w", err) 129 } 130 131 // Delete any OAuth applications associated with this token. 132 if err := p.state.DB.DeleteWhere(ctx, []db.Where{{Key: "client_id", Value: t.ClientID}}, &[]*gtsmodel.Application{}); err != nil { 133 return fmt.Errorf("deleteUserAndTokensForAccount: db error deleting application: %w", err) 134 } 135 136 // Delete the token itself. 137 if err := p.state.DB.DeleteByID(ctx, t.ID, t); err != nil { 138 return fmt.Errorf("deleteUserAndTokensForAccount: db error deleting token: %w", err) 139 } 140 } 141 142 columns, err := stubbifyUser(user) 143 if err != nil { 144 return fmt.Errorf("deleteUserAndTokensForAccount: error stubbifying user: %w", err) 145 } 146 147 if err := p.state.DB.UpdateUser(ctx, user, columns...); err != nil { 148 return fmt.Errorf("deleteUserAndTokensForAccount: db error updating user: %w", err) 149 } 150 151 return nil 152 } 153 154 // deleteAccountFollows deletes: 155 // - Follows targeting account. 156 // - Follow requests targeting account. 157 // - Follows created by account. 158 // - Follow requests created by account. 159 func (p *Processor) deleteAccountFollows(ctx context.Context, account *gtsmodel.Account) error { 160 // Delete follows targeting this account. 161 followedBy, err := p.state.DB.GetAccountFollowers(ctx, account.ID) 162 if err != nil && !errors.Is(err, db.ErrNoEntries) { 163 return fmt.Errorf("deleteAccountFollows: db error getting follows targeting account %s: %w", account.ID, err) 164 } 165 166 for _, follow := range followedBy { 167 if err := p.state.DB.DeleteFollowByID(ctx, follow.ID); err != nil { 168 return fmt.Errorf("deleteAccountFollows: db error unfollowing account followedBy: %w", err) 169 } 170 } 171 172 // Delete follow requests targeting this account. 173 followRequestedBy, err := p.state.DB.GetAccountFollowRequests(ctx, account.ID) 174 if err != nil && !errors.Is(err, db.ErrNoEntries) { 175 return fmt.Errorf("deleteAccountFollows: db error getting follow requests targeting account %s: %w", account.ID, err) 176 } 177 178 for _, followRequest := range followRequestedBy { 179 if err := p.state.DB.DeleteFollowRequestByID(ctx, followRequest.ID); err != nil { 180 return fmt.Errorf("deleteAccountFollows: db error unfollowing account followRequestedBy: %w", err) 181 } 182 } 183 184 var ( 185 // Use this slice to batch unfollow messages. 186 msgs = []messages.FromClientAPI{} 187 // To avoid checking if account is local over + over 188 // inside the subsequent loops, just generate static 189 // side effects function once now. 190 unfollowSideEffects = p.unfollowSideEffectsFunc(account) 191 ) 192 193 // Delete follows originating from this account. 194 following, err := p.state.DB.GetAccountFollows(ctx, account.ID) 195 if err != nil && !errors.Is(err, db.ErrNoEntries) { 196 return fmt.Errorf("deleteAccountFollows: db error getting follows owned by account %s: %w", account.ID, err) 197 } 198 199 // For each follow owned by this account, unfollow 200 // and process side effects (noop if remote account). 201 for _, follow := range following { 202 if err := p.state.DB.DeleteFollowByID(ctx, follow.ID); err != nil { 203 return fmt.Errorf("deleteAccountFollows: db error unfollowing account: %w", err) 204 } 205 if msg := unfollowSideEffects(ctx, account, follow); msg != nil { 206 // There was a side effect to process. 207 msgs = append(msgs, *msg) 208 } 209 } 210 211 // Delete follow requests originating from this account. 212 followRequesting, err := p.state.DB.GetAccountFollowRequesting(ctx, account.ID) 213 if err != nil && !errors.Is(err, db.ErrNoEntries) { 214 return fmt.Errorf("deleteAccountFollows: db error getting follow requests owned by account %s: %w", account.ID, err) 215 } 216 217 // For each follow owned by this account, unfollow 218 // and process side effects (noop if remote account). 219 for _, followRequest := range followRequesting { 220 if err := p.state.DB.DeleteFollowRequestByID(ctx, followRequest.ID); err != nil { 221 return fmt.Errorf("deleteAccountFollows: db error unfollowingRequesting account: %w", err) 222 } 223 224 // Dummy out a follow so our side effects func 225 // has something to work with. This follow will 226 // never enter the db, it's just for convenience. 227 follow := >smodel.Follow{ 228 URI: followRequest.URI, 229 AccountID: followRequest.AccountID, 230 Account: followRequest.Account, 231 TargetAccountID: followRequest.TargetAccountID, 232 TargetAccount: followRequest.TargetAccount, 233 } 234 235 if msg := unfollowSideEffects(ctx, account, follow); msg != nil { 236 // There was a side effect to process. 237 msgs = append(msgs, *msg) 238 } 239 } 240 241 // Process accreted messages asynchronously. 242 p.state.Workers.EnqueueClientAPI(ctx, msgs...) 243 244 return nil 245 } 246 247 func (p *Processor) unfollowSideEffectsFunc(deletedAccount *gtsmodel.Account) func(ctx context.Context, account *gtsmodel.Account, follow *gtsmodel.Follow) *messages.FromClientAPI { 248 if !deletedAccount.IsLocal() { 249 // Don't try to process side effects 250 // for accounts that aren't local. 251 return func(ctx context.Context, account *gtsmodel.Account, follow *gtsmodel.Follow) *messages.FromClientAPI { 252 return nil // noop 253 } 254 } 255 256 return func(ctx context.Context, account *gtsmodel.Account, follow *gtsmodel.Follow) *messages.FromClientAPI { 257 if follow.TargetAccount == nil { 258 // TargetAccount seems to have gone; 259 // race condition? db corruption? 260 log.WithContext(ctx).WithField("follow", follow).Warn("follow had no TargetAccount, likely race condition") 261 return nil 262 } 263 264 if follow.TargetAccount.IsLocal() { 265 // No side effects for local unfollows. 266 return nil 267 } 268 269 // There was a follow, process side effects. 270 return &messages.FromClientAPI{ 271 APObjectType: ap.ActivityFollow, 272 APActivityType: ap.ActivityUndo, 273 GTSModel: follow, 274 OriginAccount: account, 275 TargetAccount: follow.TargetAccount, 276 } 277 } 278 } 279 280 func (p *Processor) deleteAccountBlocks(ctx context.Context, account *gtsmodel.Account) error { 281 if err := p.state.DB.DeleteAccountBlocks(ctx, account.ID); err != nil { 282 return fmt.Errorf("deleteAccountBlocks: db error deleting account blocks for %s: %w", account.ID, err) 283 } 284 return nil 285 } 286 287 // deleteAccountStatuses iterates through all statuses owned by 288 // the given account, passing each discovered status (and boosts 289 // thereof) to the processor workers for further async processing. 290 func (p *Processor) deleteAccountStatuses(ctx context.Context, account *gtsmodel.Account) error { 291 // We'll select statuses 50 at a time so we don't wreck the db, 292 // and pass them through to the client api worker to handle. 293 // 294 // Deleting the statuses in this way also handles deleting the 295 // account's media attachments, mentions, and polls, since these 296 // are all attached to statuses. 297 298 var ( 299 statuses []*gtsmodel.Status 300 err error 301 maxID string 302 msgs = []messages.FromClientAPI{} 303 ) 304 305 statusLoop: 306 for { 307 // Page through account's statuses. 308 statuses, err = p.state.DB.GetAccountStatuses(ctx, account.ID, deleteSelectLimit, false, false, maxID, "", false, false) 309 if err != nil && !errors.Is(err, db.ErrNoEntries) { 310 // Make sure we don't have a real error. 311 return err 312 } 313 314 if len(statuses) == 0 { 315 break statusLoop 316 } 317 318 // Update next maxID from last status. 319 maxID = statuses[len(statuses)-1].ID 320 321 for _, status := range statuses { 322 status.Account = account // ensure account is set 323 324 // Pass the status delete through the client api worker for processing. 325 msgs = append(msgs, messages.FromClientAPI{ 326 APObjectType: ap.ObjectNote, 327 APActivityType: ap.ActivityDelete, 328 GTSModel: status, 329 OriginAccount: account, 330 TargetAccount: account, 331 }) 332 333 // Look for any boosts of this status in DB. 334 boosts, err := p.state.DB.GetStatusReblogs(ctx, status) 335 if err != nil && !errors.Is(err, db.ErrNoEntries) { 336 return fmt.Errorf("deleteAccountStatuses: error fetching status reblogs for %s: %w", status.ID, err) 337 } 338 339 for _, boost := range boosts { 340 if boost.Account == nil { 341 // Fetch the relevant account for this status boost. 342 boostAcc, err := p.state.DB.GetAccountByID(ctx, boost.AccountID) 343 if err != nil { 344 if errors.Is(err, db.ErrNoEntries) { 345 // We don't have an account for this boost 346 // for some reason, so just skip processing. 347 log.WithContext(ctx).WithField("boost", boost).Warnf("no account found with id %s for boost %s", boost.AccountID, boost.ID) 348 continue 349 } 350 return fmt.Errorf("deleteAccountStatuses: error fetching boosted status account for %s: %w", boost.AccountID, err) 351 } 352 353 // Set account model 354 boost.Account = boostAcc 355 } 356 357 // Pass the boost delete through the client api worker for processing. 358 msgs = append(msgs, messages.FromClientAPI{ 359 APObjectType: ap.ActivityAnnounce, 360 APActivityType: ap.ActivityUndo, 361 GTSModel: status, 362 OriginAccount: boost.Account, 363 TargetAccount: account, 364 }) 365 } 366 } 367 } 368 369 // Batch process all accreted messages. 370 p.state.Workers.EnqueueClientAPI(ctx, msgs...) 371 372 return nil 373 } 374 375 func (p *Processor) deleteAccountNotifications(ctx context.Context, account *gtsmodel.Account) error { 376 // Delete all notifications of all types targeting given account. 377 if err := p.state.DB.DeleteNotifications(ctx, nil, account.ID, ""); err != nil && !errors.Is(err, db.ErrNoEntries) { 378 return err 379 } 380 381 // Delete all notifications of all types originating from given account. 382 if err := p.state.DB.DeleteNotifications(ctx, nil, "", account.ID); err != nil && !errors.Is(err, db.ErrNoEntries) { 383 return err 384 } 385 386 return nil 387 } 388 389 func (p *Processor) deleteAccountPeripheral(ctx context.Context, account *gtsmodel.Account) error { 390 // Delete all bookmarks owned by given account. 391 if err := p.state.DB.DeleteStatusBookmarks(ctx, account.ID, ""); // nocollapse 392 err != nil && !errors.Is(err, db.ErrNoEntries) { 393 return err 394 } 395 396 // Delete all bookmarks targeting given account. 397 if err := p.state.DB.DeleteStatusBookmarks(ctx, "", account.ID); // nocollapse 398 err != nil && !errors.Is(err, db.ErrNoEntries) { 399 return err 400 } 401 402 // Delete all faves owned by given account. 403 if err := p.state.DB.DeleteStatusFaves(ctx, account.ID, ""); // nocollapse 404 err != nil && !errors.Is(err, db.ErrNoEntries) { 405 return err 406 } 407 408 // Delete all faves targeting given account. 409 if err := p.state.DB.DeleteStatusFaves(ctx, "", account.ID); // nocollapse 410 err != nil && !errors.Is(err, db.ErrNoEntries) { 411 return err 412 } 413 414 // TODO: add status mutes here when they're implemented. 415 416 return nil 417 } 418 419 // stubbifyAccount renders the given account as a stub, 420 // removing most information from it and marking it as 421 // suspended. 422 // 423 // The origin parameter refers to the origin of the 424 // suspension action; should be an account ID or domain 425 // block ID. 426 // 427 // For caller's convenience, this function returns the db 428 // names of all columns that are updated by it. 429 func stubbifyAccount(account *gtsmodel.Account, origin string) []string { 430 var ( 431 falseBool = func() *bool { b := false; return &b } 432 trueBool = func() *bool { b := true; return &b } 433 now = time.Now() 434 never = time.Time{} 435 ) 436 437 account.FetchedAt = never 438 account.AvatarMediaAttachmentID = "" 439 account.AvatarRemoteURL = "" 440 account.HeaderMediaAttachmentID = "" 441 account.HeaderRemoteURL = "" 442 account.DisplayName = "" 443 account.EmojiIDs = nil 444 account.Emojis = nil 445 account.Fields = nil 446 account.Note = "" 447 account.NoteRaw = "" 448 account.Memorial = falseBool() 449 account.AlsoKnownAs = "" 450 account.MovedToAccountID = "" 451 account.Reason = "" 452 account.Discoverable = falseBool() 453 account.StatusContentType = "" 454 account.CustomCSS = "" 455 account.SuspendedAt = now 456 account.SuspensionOrigin = origin 457 account.HideCollections = trueBool() 458 account.EnableRSS = falseBool() 459 460 return []string{ 461 "fetched_at", 462 "avatar_media_attachment_id", 463 "avatar_remote_url", 464 "header_media_attachment_id", 465 "header_remote_url", 466 "display_name", 467 "emojis", 468 "fields", 469 "note", 470 "note_raw", 471 "memorial", 472 "also_known_as", 473 "moved_to_account_id", 474 "reason", 475 "discoverable", 476 "status_content_type", 477 "custom_css", 478 "suspended_at", 479 "suspension_origin", 480 "hide_collections", 481 "enable_rss", 482 } 483 } 484 485 // stubbifyUser renders the given user as a stub, 486 // removing sensitive information like IP addresses 487 // and sign-in times, but keeping email addresses to 488 // prevent the same email address from creating another 489 // account on this instance. 490 // 491 // `encrypted_password` is set to the bcrypt hash of a 492 // random uuid, so if the action is reversed, the user 493 // will have to reset their password via email. 494 // 495 // For caller's convenience, this function returns the db 496 // names of all columns that are updated by it. 497 func stubbifyUser(user *gtsmodel.User) ([]string, error) { 498 uuid, err := uuid.New().MarshalBinary() 499 if err != nil { 500 return nil, err 501 } 502 503 dummyPassword, err := bcrypt.GenerateFromPassword(uuid, bcrypt.DefaultCost) 504 if err != nil { 505 return nil, err 506 } 507 508 var never = time.Time{} 509 510 user.EncryptedPassword = string(dummyPassword) 511 user.SignUpIP = net.IPv4zero 512 user.CurrentSignInAt = never 513 user.CurrentSignInIP = net.IPv4zero 514 user.LastSignInAt = never 515 user.LastSignInIP = net.IPv4zero 516 user.SignInCount = 1 517 user.Locale = "" 518 user.CreatedByApplicationID = "" 519 user.LastEmailedAt = never 520 user.ConfirmationToken = "" 521 user.ConfirmationSentAt = never 522 user.ResetPasswordToken = "" 523 user.ResetPasswordSentAt = never 524 525 return []string{ 526 "encrypted_password", 527 "sign_up_ip", 528 "current_sign_in_at", 529 "current_sign_in_ip", 530 "last_sign_in_at", 531 "last_sign_in_ip", 532 "sign_in_count", 533 "locale", 534 "created_by_application_id", 535 "last_emailed_at", 536 "confirmation_token", 537 "confirmation_sent_at", 538 "reset_password_token", 539 "reset_password_sent_at", 540 }, nil 541 }