[chore] Refactor account deleting/block logic, tidy up some other processing things (#1599)

* start refactoring account deletion

* update to use state.DB

* further messing about

* some more tidying up

* more tidying, cleaning, nice-making

* further adventures in refactoring and the woes of technical debt

* update fr accept/reject

* poking + prodding

* fix up deleting

* create fave uri

* don't log using requestingAccount.ID because it might be nil

* move getBookmarks function

* use exists query to check for status bookmark

* use deletenotifications func

* fiddle

* delete follow request notif

* split up some db functions

* Fix possible nil pointer panic

* fix more possible nil pointers

* fix license headers

* warn when follow missing (target) account

* return wrapped err when bookmark/fave models can't be retrieved

* simplify self account delete

* warn log likely race condition

* de-sillify status delete loop

* move error check due north

* warn when unfollowSideEffects has no target account

* warn when no boost account is found

* warn + dump follow when no account

* more warnings

* warn on fave account not set

* move for loop inside anonymous function

* fix funky logic

* don't remove mutual account items on block;
do make sure unfollow occurs in both directions!
This commit is contained in:
tobi
2023-03-20 19:10:08 +01:00
committed by GitHub
parent 276d773438
commit e8595f0c64
53 changed files with 2472 additions and 1321 deletions

View File

@@ -20,135 +20,322 @@ package account
import (
"context"
"errors"
"fmt"
"time"
"codeberg.org/gruf/go-kv"
"github.com/superseriousbusiness/gotosocial/internal/ap"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"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
// Delete deletes an account, and all of that account's statuses, media, follows, notifications, etc etc etc.
// 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.
func (p *Processor) Delete(ctx context.Context, account *gtsmodel.Account, origin string) gtserror.WithCode {
fields := kv.Fields{{"username", account.Username}}
if account.Domain != "" {
fields = append(fields, kv.Field{
"domain", account.Domain,
})
}
l := log.WithContext(ctx).WithFields(fields...)
l := log.WithContext(ctx).WithFields(kv.Fields{
{"username", account.Username},
{"domain", account.Domain},
}...)
l.Trace("beginning account delete process")
// 1. Delete account's application(s), clients, and oauth tokens
// we only need to do this step for local account since remote ones won't have any tokens or applications on our server
var user *gtsmodel.User
if account.Domain == "" {
// see if we can get a user for this account
var err error
if user, err = p.state.DB.GetUserByAccountID(ctx, account.ID); err == nil {
// we got one! select all tokens with the user's ID
tokens := []*gtsmodel.Token{}
if err := p.state.DB.GetWhere(ctx, []db.Where{{Key: "user_id", Value: user.ID}}, &tokens); err == nil {
// we have some tokens to delete
for _, t := range tokens {
// delete client(s) associated with this token
if err := p.state.DB.DeleteByID(ctx, t.ClientID, &gtsmodel.Client{}); err != nil {
l.Errorf("error deleting oauth client: %s", err)
}
// delete application(s) associated with this token
if err := p.state.DB.DeleteWhere(ctx, []db.Where{{Key: "client_id", Value: t.ClientID}}, &gtsmodel.Application{}); err != nil {
l.Errorf("error deleting application: %s", err)
}
// delete the token itself
if err := p.state.DB.DeleteByID(ctx, t.ID, t); err != nil {
l.Errorf("error deleting oauth token: %s", err)
}
}
}
if account.IsLocal() {
if err := p.deleteUserAndTokensForAccount(ctx, account); err != nil {
return gtserror.NewErrorInternalError(err)
}
}
// 2. Delete account's blocks
l.Trace("deleting account blocks")
// first delete any blocks that this account created
if err := p.state.DB.DeleteBlocksByOriginAccountID(ctx, account.ID); err != nil {
l.Errorf("error deleting blocks created by account: %s", err)
if err := p.deleteAccountFollows(ctx, account); err != nil {
return gtserror.NewErrorInternalError(err)
}
// now delete any blocks that target this account
if err := p.state.DB.DeleteBlocksByTargetAccountID(ctx, account.ID); err != nil {
l.Errorf("error deleting blocks targeting account: %s", err)
if err := p.deleteAccountBlocks(ctx, account); err != nil {
return gtserror.NewErrorInternalError(err)
}
// 3. Delete account's emoji
// nothing to do here
// 4. Delete account's follow requests
// TODO: federate these if necessary
l.Trace("deleting account follow requests")
// first delete any follow requests that this account created
if err := p.state.DB.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.FollowRequest{}); err != nil {
l.Errorf("error deleting follow requests created by account: %s", err)
if err := p.deleteAccountStatuses(ctx, account); err != nil {
return gtserror.NewErrorInternalError(err)
}
// now delete any follow requests that target this account
if err := p.state.DB.DeleteWhere(ctx, []db.Where{{Key: "target_account_id", Value: account.ID}}, &[]*gtsmodel.FollowRequest{}); err != nil {
l.Errorf("error deleting follow requests targeting account: %s", err)
if err := p.deleteAccountNotifications(ctx, account); err != nil {
return gtserror.NewErrorInternalError(err)
}
// 5. Delete account's follows
// TODO: federate these if necessary
l.Trace("deleting account follows")
// first delete any follows that this account created
if err := p.state.DB.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.Follow{}); err != nil {
l.Errorf("error deleting follows created by account: %s", err)
if err := p.deleteAccountPeripheral(ctx, account); err != nil {
return gtserror.NewErrorInternalError(err)
}
// now delete any follows that target this account
if err := p.state.DB.DeleteWhere(ctx, []db.Where{{Key: "target_account_id", Value: account.ID}}, &[]*gtsmodel.Follow{}); err != nil {
l.Errorf("error deleting follows targeting account: %s", err)
// To prevent the account being created again,
// stubbify it and update it in the db.
// The account will not be deleted, but it
// will become completely unusable.
columns := stubbifyAccount(account, origin)
if err := p.state.DB.UpdateAccount(ctx, account, columns...); err != nil {
return gtserror.NewErrorInternalError(err)
}
var maxID string
l.Info("account deleted")
return nil
}
// 6. Delete account's statuses
l.Trace("deleting account statuses")
// DeleteSelf is like Delete, but specifically for local accounts deleting themselves.
//
// Calling DeleteSelf results in a delete message being enqueued in the processor,
// which causes side effects to occur: delete will be federated out to other instances,
// and the above Delete function will be called afterwards from the processor, to clear
// out the account's bits and bobs, and stubbify it.
func (p *Processor) DeleteSelf(ctx context.Context, account *gtsmodel.Account) gtserror.WithCode {
fromClientAPIMessage := messages.FromClientAPI{
APObjectType: ap.ActorPerson,
APActivityType: ap.ActivityDelete,
OriginAccount: account,
TargetAccount: account,
}
// we'll select statuses 20 at a time so we don't wreck the db, and pass them through to the client api channel
// Deleting the statuses in this way also handles 7. Delete account's media attachments, 8. Delete account's mentions, and 9. Delete account's polls,
// since these are all attached to statuses.
// Process the delete side effects asynchronously.
p.state.Workers.EnqueueClientAPI(ctx, fromClientAPIMessage)
for {
// Fetch next block of account statuses from database
statuses, err := p.state.DB.GetAccountStatuses(ctx, account.ID, 20, false, false, maxID, "", false, false)
return nil
}
// deleteUserAndTokensForAccount deletes the gtsmodel.User and
// any OAuth tokens and applications for the given account.
//
// Callers to this function should already have checked that
// this is a local account, or else it won't have a user associated
// with it, and this will fail.
func (p *Processor) deleteUserAndTokensForAccount(ctx context.Context, account *gtsmodel.Account) error {
user, err := p.state.DB.GetUserByAccountID(ctx, account.ID)
if err != nil {
return fmt.Errorf("deleteUserAndTokensForAccount: db error getting user: %w", err)
}
tokens := []*gtsmodel.Token{}
if err := p.state.DB.GetWhere(ctx, []db.Where{{Key: "user_id", Value: user.ID}}, &tokens); err != nil {
return fmt.Errorf("deleteUserAndTokensForAccount: db error getting tokens: %w", err)
}
for _, t := range tokens {
// Delete any OAuth clients associated with this token.
if err := p.state.DB.DeleteByID(ctx, t.ClientID, &[]*gtsmodel.Client{}); err != nil {
return fmt.Errorf("deleteUserAndTokensForAccount: db error deleting client: %w", err)
}
// Delete any OAuth applications associated with this token.
if err := p.state.DB.DeleteWhere(ctx, []db.Where{{Key: "client_id", Value: t.ClientID}}, &[]*gtsmodel.Application{}); err != nil {
return fmt.Errorf("deleteUserAndTokensForAccount: db error deleting application: %w", err)
}
// Delete the token itself.
if err := p.state.DB.DeleteByID(ctx, t.ID, t); err != nil {
return fmt.Errorf("deleteUserAndTokensForAccount: db error deleting token: %w", err)
}
}
if err := p.state.DB.DeleteUserByID(ctx, user.ID); err != nil {
return fmt.Errorf("deleteUserAndTokensForAccount: db error deleting user: %w", err)
}
return nil
}
// deleteAccountFollows deletes:
// - Follows targeting account.
// - Follow requests targeting account.
// - Follows created by account.
// - Follow requests created by account.
func (p *Processor) deleteAccountFollows(ctx context.Context, account *gtsmodel.Account) error {
// Delete follows targeting this account.
followedBy, err := p.state.DB.GetFollows(ctx, "", account.ID)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return fmt.Errorf("deleteAccountFollows: db error getting follows targeting account %s: %w", account.ID, err)
}
for _, follow := range followedBy {
if _, err := p.state.DB.Unfollow(ctx, follow.AccountID, account.ID); err != nil {
return fmt.Errorf("deleteAccountFollows: db error unfollowing account followedBy: %w", err)
}
}
// Delete follow requests targeting this account.
followRequestedBy, err := p.state.DB.GetFollowRequests(ctx, "", account.ID)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return fmt.Errorf("deleteAccountFollows: db error getting follow requests targeting account %s: %w", account.ID, err)
}
for _, followRequest := range followRequestedBy {
if _, err := p.state.DB.UnfollowRequest(ctx, followRequest.AccountID, account.ID); err != nil {
return fmt.Errorf("deleteAccountFollows: db error unfollowing account followRequestedBy: %w", err)
}
}
var (
// Use this slice to batch unfollow messages.
msgs = []messages.FromClientAPI{}
// To avoid checking if account is local over + over
// inside the subsequent loops, just generate static
// side effects function once now.
unfollowSideEffects = p.unfollowSideEffectsFunc(account)
)
// Delete follows originating from this account.
following, err := p.state.DB.GetFollows(ctx, account.ID, "")
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return fmt.Errorf("deleteAccountFollows: db error getting follows owned by account %s: %w", account.ID, err)
}
// For each follow owned by this account, unfollow
// and process side effects (noop if remote account).
for _, follow := range following {
if uri, err := p.state.DB.Unfollow(ctx, account.ID, follow.TargetAccountID); err != nil {
return fmt.Errorf("deleteAccountFollows: db error unfollowing account: %w", err)
} else if uri == "" {
// There was no follow after all.
// Some race condition? Skip.
log.WithContext(ctx).WithField("follow", follow).Warn("Unfollow did not return uri, likely race condition")
continue
}
if msg := unfollowSideEffects(ctx, account, follow); msg != nil {
// There was a side effect to process.
msgs = append(msgs, *msg)
}
}
// Delete follow requests originating from this account.
followRequesting, err := p.state.DB.GetFollowRequests(ctx, account.ID, "")
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return fmt.Errorf("deleteAccountFollows: db error getting follow requests owned by account %s: %w", account.ID, err)
}
// For each follow owned by this account, unfollow
// and process side effects (noop if remote account).
for _, followRequest := range followRequesting {
uri, err := p.state.DB.UnfollowRequest(ctx, account.ID, followRequest.TargetAccountID)
if err != nil {
if !errors.Is(err, db.ErrNoEntries) {
// an actual error has occurred
l.Errorf("Delete: db error selecting statuses for account %s: %s", account.Username, err)
}
break
return fmt.Errorf("deleteAccountFollows: db error unfollowRequesting account: %w", err)
}
if uri == "" {
// There was no follow request after all.
// Some race condition? Skip.
log.WithContext(ctx).WithField("followRequest", followRequest).Warn("UnfollowRequest did not return uri, likely race condition")
continue
}
// Dummy out a follow so our side effects func
// has something to work with. This follow will
// never enter the db, it's just for convenience.
follow := &gtsmodel.Follow{
URI: uri,
AccountID: followRequest.AccountID,
Account: followRequest.Account,
TargetAccountID: followRequest.TargetAccountID,
TargetAccount: followRequest.TargetAccount,
}
if msg := unfollowSideEffects(ctx, account, follow); msg != nil {
// There was a side effect to process.
msgs = append(msgs, *msg)
}
}
// Process accreted messages asynchronously.
p.state.Workers.EnqueueClientAPI(ctx, msgs...)
return nil
}
func (p *Processor) unfollowSideEffectsFunc(deletedAccount *gtsmodel.Account) func(ctx context.Context, account *gtsmodel.Account, follow *gtsmodel.Follow) *messages.FromClientAPI {
if !deletedAccount.IsLocal() {
// Don't try to process side effects
// for accounts that aren't local.
return func(ctx context.Context, account *gtsmodel.Account, follow *gtsmodel.Follow) *messages.FromClientAPI {
return nil // noop
}
}
return func(ctx context.Context, account *gtsmodel.Account, follow *gtsmodel.Follow) *messages.FromClientAPI {
if follow.TargetAccount == nil {
// TargetAccount seems to have gone;
// race condition? db corruption?
log.WithContext(ctx).WithField("follow", follow).Warn("follow had no TargetAccount, likely race condition")
return nil
}
if follow.TargetAccount.IsLocal() {
// No side effects for local unfollows.
return nil
}
// There was a follow, process side effects.
return &messages.FromClientAPI{
APObjectType: ap.ActivityFollow,
APActivityType: ap.ActivityUndo,
GTSModel: follow,
OriginAccount: account,
TargetAccount: follow.TargetAccount,
}
}
}
func (p *Processor) deleteAccountBlocks(ctx context.Context, account *gtsmodel.Account) error {
// Delete blocks created by this account.
if err := p.state.DB.DeleteBlocksByOriginAccountID(ctx, account.ID); err != nil {
return fmt.Errorf("deleteAccountBlocks: db error deleting blocks created by account %s: %w", account.ID, err)
}
// Delete blocks targeting this account.
if err := p.state.DB.DeleteBlocksByTargetAccountID(ctx, account.ID); err != nil {
return fmt.Errorf("deleteAccountBlocks: db error deleting blocks targeting account %s: %w", account.ID, err)
}
return nil
}
// deleteAccountStatuses iterates through all statuses owned by
// the given account, passing each discovered status (and boosts
// thereof) to the processor workers for further async processing.
func (p *Processor) deleteAccountStatuses(ctx context.Context, account *gtsmodel.Account) error {
// We'll select statuses 50 at a time so we don't wreck the db,
// and pass them through to the client api worker to handle.
//
// Deleting the statuses in this way also handles deleting the
// account's media attachments, mentions, and polls, since these
// are all attached to statuses.
var (
statuses []*gtsmodel.Status
err error
maxID string
msgs = []messages.FromClientAPI{}
)
statusLoop:
for {
// Page through account's statuses.
statuses, err = p.state.DB.GetAccountStatuses(ctx, account.ID, deleteSelectLimit, false, false, maxID, "", false, false)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
// Make sure we don't have a real error.
return err
}
if len(statuses) == 0 {
break // reached end
break statusLoop
}
// Update next maxID from last status.
maxID = statuses[len(statuses)-1].ID
for _, status := range statuses {
// Ensure account is set
status.Account = account
status.Account = account // ensure account is set
l.Tracef("queue client API status delete: %s", status.ID)
// pass the status delete through the client api channel for processing
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
// Pass the status delete through the client api worker for processing.
msgs = append(msgs, messages.FromClientAPI{
APObjectType: ap.ObjectNote,
APActivityType: ap.ActivityDelete,
GTSModel: status,
@@ -156,30 +343,32 @@ func (p *Processor) Delete(ctx context.Context, account *gtsmodel.Account, origi
TargetAccount: account,
})
// Look for any boosts of this status in DB
// Look for any boosts of this status in DB.
boosts, err := p.state.DB.GetStatusReblogs(ctx, status)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
l.Errorf("error fetching status reblogs for %q: %v", status.ID, err)
continue
return fmt.Errorf("deleteAccountStatuses: error fetching status reblogs for %s: %w", status.ID, err)
}
for _, boost := range boosts {
if boost.Account == nil {
// Fetch the relevant account for this status boost
// Fetch the relevant account for this status boost.
boostAcc, err := p.state.DB.GetAccountByID(ctx, boost.AccountID)
if err != nil {
l.Errorf("error fetching boosted status account for %q: %v", boost.AccountID, err)
continue
if errors.Is(err, db.ErrNoEntries) {
// We don't have an account for this boost
// for some reason, so just skip processing.
log.WithContext(ctx).WithField("boost", boost).Warnf("no account found with id %s for boost %s", boost.AccountID, boost.ID)
continue
}
return fmt.Errorf("deleteAccountStatuses: error fetching boosted status account for %s: %w", boost.AccountID, err)
}
// Set account model
boost.Account = boostAcc
}
l.Tracef("queue client API boost delete: %s", status.ID)
// pass the boost delete through the client api channel for processing
p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
// Pass the boost delete through the client api worker for processing.
msgs = append(msgs, messages.FromClientAPI{
APObjectType: ap.ActivityAnnounce,
APActivityType: ap.ActivityUndo,
GTSModel: status,
@@ -188,128 +377,120 @@ func (p *Processor) Delete(ctx context.Context, account *gtsmodel.Account, origi
})
}
}
// Update next maxID from last status
maxID = statuses[len(statuses)-1].ID
}
// 10. Delete account's notifications
l.Trace("deleting account notifications")
// first notifications created by account
if err := p.state.DB.DeleteWhere(ctx, []db.Where{{Key: "origin_account_id", Value: account.ID}}, &[]*gtsmodel.Notification{}); err != nil {
l.Errorf("error deleting notifications created by account: %s", err)
// Batch process all accreted messages.
p.state.Workers.EnqueueClientAPI(ctx, msgs...)
return nil
}
func (p *Processor) deleteAccountNotifications(ctx context.Context, account *gtsmodel.Account) error {
// Delete all notifications targeting given account.
if err := p.state.DB.DeleteNotifications(ctx, account.ID, ""); err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// now notifications targeting account
if err := p.state.DB.DeleteWhere(ctx, []db.Where{{Key: "target_account_id", Value: account.ID}}, &[]*gtsmodel.Notification{}); err != nil {
l.Errorf("error deleting notifications targeting account: %s", err)
// Delete all notifications originating from given account.
if err := p.state.DB.DeleteNotifications(ctx, "", account.ID); err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// 11. Delete account's bookmarks
l.Trace("deleting account bookmarks")
if err := p.state.DB.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.StatusBookmark{}); err != nil {
l.Errorf("error deleting bookmarks created by account: %s", err)
return nil
}
func (p *Processor) deleteAccountPeripheral(ctx context.Context, account *gtsmodel.Account) error {
// Delete all bookmarks owned by given account.
if err := p.state.DB.DeleteStatusBookmarks(ctx, account.ID, ""); // nocollapse
err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// 12. Delete account's faves
// TODO: federate these if necessary
l.Trace("deleting account faves")
if err := p.state.DB.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.StatusFave{}); err != nil {
l.Errorf("error deleting faves created by account: %s", err)
// Delete all bookmarks targeting given account.
if err := p.state.DB.DeleteStatusBookmarks(ctx, "", account.ID); // nocollapse
err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// 13. Delete account's mutes
l.Trace("deleting account mutes")
if err := p.state.DB.DeleteWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, &[]*gtsmodel.StatusMute{}); err != nil {
l.Errorf("error deleting status mutes created by account: %s", err)
// Delete all faves owned by given account.
if err := p.state.DB.DeleteStatusFaves(ctx, account.ID, ""); // nocollapse
err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// 14. Delete account's streams
// TODO
// 15. Delete account's tags
// TODO
// 16. Delete account's user
if user != nil {
l.Trace("deleting account user")
if err := p.state.DB.DeleteUserByID(ctx, user.ID); err != nil {
return gtserror.NewErrorInternalError(err)
}
// Delete all faves targeting given account.
if err := p.state.DB.DeleteStatusFaves(ctx, "", account.ID); // nocollapse
err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// 17. Delete account's timeline
// TODO
// TODO: add status mutes here when they're implemented.
// 18. Delete account itself
// to prevent the account being created again, set all these fields and update it in the db
// the account won't actually be *removed* from the database but it will be set to just a stub
account.Note = ""
account.DisplayName = ""
return nil
}
// stubbifyAccount renders the given account as a stub,
// removing most information from it and marking it as
// suspended.
//
// The origin parameter refers to the origin of the
// suspension action; should be an account ID or domain
// block ID.
//
// For caller's convenience, this function returns the db
// names of all columns that are updated by it.
func stubbifyAccount(account *gtsmodel.Account, origin string) []string {
var (
falseBool = func() *bool { b := false; return &b }
trueBool = func() *bool { b := true; return &b }
now = time.Now()
never = time.Time{}
)
account.FetchedAt = never
account.AvatarMediaAttachmentID = ""
account.AvatarRemoteURL = ""
account.HeaderMediaAttachmentID = ""
account.HeaderRemoteURL = ""
account.DisplayName = ""
account.EmojiIDs = nil
account.Emojis = nil
account.Fields = nil
account.Note = ""
account.NoteRaw = ""
account.Memorial = falseBool()
account.AlsoKnownAs = ""
account.MovedToAccountID = ""
account.Reason = ""
account.Emojis = []*gtsmodel.Emoji{}
account.EmojiIDs = []string{}
account.Fields = []gtsmodel.Field{}
hideCollections := true
account.HideCollections = &hideCollections
discoverable := false
account.Discoverable = &discoverable
account.SuspendedAt = time.Now()
account.Discoverable = falseBool()
account.StatusContentType = ""
account.CustomCSS = ""
account.SuspendedAt = now
account.SuspensionOrigin = origin
err := p.state.DB.UpdateAccount(ctx, account)
if err != nil {
return gtserror.NewErrorInternalError(err)
}
account.HideCollections = trueBool()
account.EnableRSS = falseBool()
l.Infof("deleted account with username %s from domain %s", account.Username, account.Domain)
return nil
}
// DeleteLocal is like Delete, but specifically for deletion of local accounts rather than federated ones.
// Unlike Delete, it will propagate the deletion out across the federating API to other instances.
func (p *Processor) DeleteLocal(ctx context.Context, account *gtsmodel.Account, form *apimodel.AccountDeleteRequest) gtserror.WithCode {
fromClientAPIMessage := messages.FromClientAPI{
APObjectType: ap.ActorPerson,
APActivityType: ap.ActivityDelete,
TargetAccount: account,
}
if form.DeleteOriginID == account.ID {
// the account owner themself has requested deletion via the API, get their user from the db
user, err := p.state.DB.GetUserByAccountID(ctx, account.ID)
if err != nil {
return gtserror.NewErrorInternalError(err)
}
// now check that the password they supplied is correct
// make sure a password is actually set and bail if not
if user.EncryptedPassword == "" {
return gtserror.NewErrorForbidden(errors.New("user password was not set"))
}
// compare the provided password with the encrypted one from the db, bail if they don't match
if err := bcrypt.CompareHashAndPassword([]byte(user.EncryptedPassword), []byte(form.Password)); err != nil {
return gtserror.NewErrorForbidden(errors.New("invalid password"))
}
fromClientAPIMessage.OriginAccount = account
} else {
// the delete has been requested by some other account, grab it;
// if we've reached this point we know it has permission already
requestingAccount, err := p.state.DB.GetAccountByID(ctx, form.DeleteOriginID)
if err != nil {
return gtserror.NewErrorInternalError(err)
}
fromClientAPIMessage.OriginAccount = requestingAccount
}
// put the delete in the processor queue to handle the rest of it asynchronously
p.state.Workers.EnqueueClientAPI(ctx, fromClientAPIMessage)
return nil
return []string{
"fetched_at",
"avatar_media_attachment_id",
"avatar_remote_url",
"header_media_attachment_id",
"header_remote_url",
"display_name",
"emojis",
"fields",
"note",
"note_raw",
"memorial",
"also_known_as",
"moved_to_account_id",
"reason",
"discoverable",
"status_content_type",
"custom_css",
"suspended_at",
"suspension_origin",
"hide_collections",
"enable_rss",
}
}