| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | // 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 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							|  |  |  | 	"slices" | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | 
					
						
							|  |  |  | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 
					
						
							| 
									
										
										
										
											2025-02-26 13:04:55 +01:00
										 |  |  | 	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/federation/dereferencing" | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 
					
						
							|  |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/id" | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/uris" | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	"golang.org/x/crypto/bcrypt" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p *Processor) MoveSelf( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							| 
									
										
										
										
											2025-02-26 13:04:55 +01:00
										 |  |  | 	authed *apiutil.Auth, | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	form *apimodel.AccountMoveRequest, | 
					
						
							|  |  |  | ) gtserror.WithCode { | 
					
						
							|  |  |  | 	// Ensure valid MovedToURI. | 
					
						
							|  |  |  | 	if form.MovedToURI == "" { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		const text = "no moved_to_uri provided in Move request" | 
					
						
							|  |  |  | 		return gtserror.NewErrorBadRequest(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	targetAcctURIStr := form.MovedToURI | 
					
						
							|  |  |  | 	targetAcctURI, err := url.Parse(form.MovedToURI) | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err := fmt.Errorf("invalid moved_to_uri provided in account Move request: %w", err) | 
					
						
							|  |  |  | 		return gtserror.NewErrorBadRequest(err, err.Error()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	if targetAcctURI.Scheme != "https" && targetAcctURI.Scheme != "http" { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		const text = "invalid move_to_uri in Move request: scheme must be http(s)" | 
					
						
							|  |  |  | 		return gtserror.NewErrorBadRequest(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 	// Self account Move requires | 
					
						
							|  |  |  | 	// password to ensure it's for real. | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	if form.Password == "" { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		const text = "no password provided in Move request" | 
					
						
							|  |  |  | 		return gtserror.NewErrorBadRequest(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := bcrypt.CompareHashAndPassword( | 
					
						
							|  |  |  | 		[]byte(authed.User.EncryptedPassword), | 
					
						
							|  |  |  | 		[]byte(form.Password), | 
					
						
							|  |  |  | 	); err != nil { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		const text = "invalid password provided in Move request" | 
					
						
							|  |  |  | 		return gtserror.NewErrorBadRequest(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	// We can't/won't validate Move activities | 
					
						
							|  |  |  | 	// to domains we have blocked, so check this. | 
					
						
							|  |  |  | 	targetDomainBlocked, err := p.state.DB.IsDomainBlocked(ctx, targetAcctURI.Host) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		err := gtserror.Newf( | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 			"db error checking if target domain %s blocked: %w", | 
					
						
							|  |  |  | 			targetAcctURI.Host, err, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if targetDomainBlocked { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		text := fmt.Sprintf( | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 			"domain of %s is blocked from this instance; "+ | 
					
						
							|  |  |  | 				"you will not be able to Move to that account", | 
					
						
							|  |  |  | 			targetAcctURIStr, | 
					
						
							|  |  |  | 		) | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	var ( | 
					
						
							|  |  |  | 		// Current account from which | 
					
						
							|  |  |  | 		// the move is taking place. | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 		originAcct = authed.Account | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Target account to which | 
					
						
							|  |  |  | 		// the move is taking place. | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 		targetAcct *gtsmodel.Account | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// AP representation of target. | 
					
						
							|  |  |  | 		targetAcctable ap.Accountable | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	// Next steps involve checking + setting | 
					
						
							|  |  |  | 	// state that might get messed up if a | 
					
						
							|  |  |  | 	// client triggers this function twice | 
					
						
							|  |  |  | 	// in quick succession, so get a lock on | 
					
						
							|  |  |  | 	// this account. | 
					
						
							|  |  |  | 	lockKey := originAcct.URI | 
					
						
							| 
									
										
										
										
											2024-05-02 14:43:00 +02:00
										 |  |  | 	unlock := p.state.ProcessingLocks.Lock(lockKey) | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	defer unlock() | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Ensure we have a valid, up-to-date representation of the target account. | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	targetAcct, targetAcctable, err = p.federator.GetAccountByURI( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							|  |  |  | 		originAcct.Username, | 
					
						
							|  |  |  | 		targetAcctURI, | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		const text = "error dereferencing moved_to_uri" | 
					
						
							|  |  |  | 		err := gtserror.Newf("error dereferencing move_to_uri: %w", err) | 
					
						
							|  |  |  | 		return gtserror.NewErrorUnprocessableEntity(err, text) | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	if !targetAcct.SuspendedAt.IsZero() { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		text := fmt.Sprintf( | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 			"target account %s is suspended from this instance; "+ | 
					
						
							|  |  |  | 				"you will not be able to Move to that account", | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 			targetAcct.URI, | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 		) | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 	if targetAcctable == nil { | 
					
						
							|  |  |  | 		// Target account was not dereferenced, now | 
					
						
							|  |  |  | 		// force refresh Move target account to ensure we | 
					
						
							|  |  |  | 		// have most up-to-date version (non remote = no-op). | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 		targetAcct, _, err = p.federator.RefreshAccount(ctx, | 
					
						
							|  |  |  | 			originAcct.Username, | 
					
						
							|  |  |  | 			targetAcct, | 
					
						
							|  |  |  | 			targetAcctable, | 
					
						
							|  |  |  | 			dereferencing.Freshest, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 			const text = "error dereferencing moved_to_uri" | 
					
						
							|  |  |  | 			err := gtserror.Newf("error dereferencing move_to_uri: %w", err) | 
					
						
							|  |  |  | 			return gtserror.NewErrorUnprocessableEntity(err, text) | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-13 14:45:50 +01:00
										 |  |  | 	// If originAcct has already moved, ensure | 
					
						
							|  |  |  | 	// this move reattempt is to the same account. | 
					
						
							|  |  |  | 	if originAcct.IsMoving() && | 
					
						
							|  |  |  | 		originAcct.MovedToURI != targetAcct.URI { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		text := fmt.Sprintf( | 
					
						
							| 
									
										
										
										
											2024-03-13 14:45:50 +01:00
										 |  |  | 			"your account is already Moving or has Moved to %s; you cannot also Move to %s", | 
					
						
							|  |  |  | 			originAcct.MovedToURI, targetAcct.URI, | 
					
						
							|  |  |  | 		) | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2024-03-13 14:45:50 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	// Target account MUST be aliased to this | 
					
						
							|  |  |  | 	// account for this to be a valid Move. | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	if !slices.Contains(targetAcct.AlsoKnownAsURIs, originAcct.URI) { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		text := fmt.Sprintf( | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 			"target account %s is not aliased to this account via alsoKnownAs; "+ | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 				"if you just changed it, please wait a few minutes and try the Move again", | 
					
						
							|  |  |  | 			targetAcct.URI, | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 		) | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Target account cannot itself have | 
					
						
							|  |  |  | 	// already Moved somewhere else. | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	if targetAcct.MovedToURI != "" { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		text := fmt.Sprintf( | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 			"target account %s has already Moved somewhere else (%s); "+ | 
					
						
							|  |  |  | 				"you will not be able to Move to that account", | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 			targetAcct.URI, targetAcct.MovedToURI, | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 		) | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check this isn't a recursive loop of moves. | 
					
						
							|  |  |  | 	if errWithCode := p.checkMoveRecursion(ctx, | 
					
						
							|  |  |  | 		originAcct, | 
					
						
							|  |  |  | 		targetAcct, | 
					
						
							|  |  |  | 	); errWithCode != nil { | 
					
						
							|  |  |  | 		return errWithCode | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	// If a Move has been *attempted* within last 5m, | 
					
						
							|  |  |  | 	// that involved the origin and target in any way, | 
					
						
							|  |  |  | 	// then we shouldn't try to reprocess immediately. | 
					
						
							|  |  |  | 	latestMoveAttempt, err := p.state.DB.GetLatestMoveAttemptInvolvingURIs( | 
					
						
							|  |  |  | 		ctx, originAcct.URI, targetAcct.URI, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		err := gtserror.Newf( | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 			"error checking latest Move attempt involving origin %s and target %s: %w", | 
					
						
							|  |  |  | 			originAcct.URI, targetAcct.URI, err, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !latestMoveAttempt.IsZero() && | 
					
						
							|  |  |  | 		time.Since(latestMoveAttempt) < 5*time.Minute { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		text := fmt.Sprintf( | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 			"your account or target account have been involved in a Move attempt within "+ | 
					
						
							|  |  |  | 				"the last 5 minutes, will not process Move; please try again after %s", | 
					
						
							|  |  |  | 			latestMoveAttempt.Add(5*time.Minute), | 
					
						
							|  |  |  | 		) | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If a Move has *succeeded* within the last week | 
					
						
							|  |  |  | 	// that involved the origin and target in any way, | 
					
						
							|  |  |  | 	// then we shouldn't process again for a while. | 
					
						
							|  |  |  | 	latestMoveSuccess, err := p.state.DB.GetLatestMoveSuccessInvolvingURIs( | 
					
						
							|  |  |  | 		ctx, originAcct.URI, targetAcct.URI, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		err := gtserror.Newf( | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 			"error checking latest Move success involving origin %s and target %s: %w", | 
					
						
							|  |  |  | 			originAcct.URI, targetAcct.URI, err, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !latestMoveSuccess.IsZero() && | 
					
						
							|  |  |  | 		time.Since(latestMoveSuccess) < 168*time.Hour { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		text := fmt.Sprintf( | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 			"your account or target account have been involved in a successful Move within "+ | 
					
						
							|  |  |  | 				"the last 7 days, will not process Move; please try again after %s", | 
					
						
							|  |  |  | 			latestMoveSuccess.Add(168*time.Hour), | 
					
						
							|  |  |  | 		) | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 		return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// See if we have a Move stored already | 
					
						
							|  |  |  | 	// or if we need to create a new one. | 
					
						
							|  |  |  | 	var move *gtsmodel.Move | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if originAcct.MoveID != "" { | 
					
						
							|  |  |  | 		// Move already stored, ensure it's | 
					
						
							|  |  |  | 		// to the target and nothing weird is | 
					
						
							|  |  |  | 		// happening with race conditions etc. | 
					
						
							|  |  |  | 		move = originAcct.Move | 
					
						
							|  |  |  | 		if move == nil { | 
					
						
							|  |  |  | 			// This shouldn't happen... | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 			err := gtserror.Newf("error fetching move %s (was nil)", originAcct.MovedToURI) | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 			return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if move.OriginURI != originAcct.URI || | 
					
						
							|  |  |  | 			move.TargetURI != targetAcct.URI { | 
					
						
							|  |  |  | 			// This is also weird... | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 			const text = "existing stored Move contains invalid fields" | 
					
						
							|  |  |  | 			return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if originAcct.MovedToURI != move.TargetURI { | 
					
						
							|  |  |  | 			// Huh... I'll be damned. | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 			const text = "existing stored Move target URI != moved_to_uri" | 
					
						
							|  |  |  | 			return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// Move not stored yet, create it. | 
					
						
							|  |  |  | 		moveID := id.NewULID() | 
					
						
							|  |  |  | 		moveURIStr := uris.GenerateURIForMove(originAcct.Username, moveID) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// We might have selected the target | 
					
						
							|  |  |  | 		// using the URL and not the URI. | 
					
						
							|  |  |  | 		// Ensure we continue with the URI! | 
					
						
							|  |  |  | 		if targetAcctURIStr != targetAcct.URI { | 
					
						
							|  |  |  | 			targetAcctURIStr = targetAcct.URI | 
					
						
							|  |  |  | 			targetAcctURI, err = url.Parse(targetAcctURIStr) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Parse origin URI. | 
					
						
							|  |  |  | 		originAcctURI, err := url.Parse(originAcct.URI) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Store the Move. | 
					
						
							|  |  |  | 		move = >smodel.Move{ | 
					
						
							|  |  |  | 			ID:          moveID, | 
					
						
							|  |  |  | 			AttemptedAt: time.Now(), | 
					
						
							|  |  |  | 			OriginURI:   originAcct.URI, | 
					
						
							|  |  |  | 			Origin:      originAcctURI, | 
					
						
							|  |  |  | 			TargetURI:   targetAcctURIStr, | 
					
						
							|  |  |  | 			Target:      targetAcctURI, | 
					
						
							|  |  |  | 			URI:         moveURIStr, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if err := p.state.DB.PutMove(ctx, move); err != nil { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 			err := gtserror.Newf("db error storing move %s: %w", moveURIStr, err) | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 			return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Update account with the new | 
					
						
							|  |  |  | 		// Move, and set moved_to_uri. | 
					
						
							|  |  |  | 		originAcct.MoveID = move.ID | 
					
						
							|  |  |  | 		originAcct.Move = move | 
					
						
							|  |  |  | 		originAcct.MovedToURI = targetAcct.URI | 
					
						
							|  |  |  | 		originAcct.MovedTo = targetAcct | 
					
						
							|  |  |  | 		if err := p.state.DB.UpdateAccount( | 
					
						
							|  |  |  | 			ctx, | 
					
						
							|  |  |  | 			originAcct, | 
					
						
							|  |  |  | 			"move_id", | 
					
						
							|  |  |  | 			"moved_to_uri", | 
					
						
							|  |  |  | 		); err != nil { | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 			err := gtserror.Newf("db error updating account: %w", err) | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 			return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Everything seems OK, process Move side effects async. | 
					
						
							| 
									
										
										
										
											2024-04-26 13:50:46 +01:00
										 |  |  | 	p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{ | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 		APObjectType:   ap.ActorPerson, | 
					
						
							|  |  |  | 		APActivityType: ap.ActivityMove, | 
					
						
							| 
									
										
										
										
											2024-03-13 13:53:29 +01:00
										 |  |  | 		GTSModel:       move, | 
					
						
							| 
									
										
										
										
											2024-04-26 13:50:46 +01:00
										 |  |  | 		Origin:         originAcct, | 
					
						
							|  |  |  | 		Target:         targetAcct, | 
					
						
							| 
									
										
										
										
											2024-01-16 17:22:44 +01:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-06-10 18:42:41 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | // checkMoveRecursion checks that a move from origin to target would | 
					
						
							|  |  |  | // not cause a loop of account moved_from_uris pointing in a loop. | 
					
						
							|  |  |  | func (p *Processor) checkMoveRecursion( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	origin *gtsmodel.Account, | 
					
						
							|  |  |  | 	target *gtsmodel.Account, | 
					
						
							|  |  |  | ) gtserror.WithCode { | 
					
						
							|  |  |  | 	// We only ever need barebones models. | 
					
						
							|  |  |  | 	ctx = gtscontext.SetBarebones(ctx) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Stack based account move following loop. | 
					
						
							|  |  |  | 	stack := []*gtsmodel.Account{origin} | 
					
						
							|  |  |  | 	checked := make(map[string]struct{}) | 
					
						
							|  |  |  | 	for len(stack) > 0 { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Pop account from stack. | 
					
						
							|  |  |  | 		next := stack[len(stack)-1] | 
					
						
							|  |  |  | 		stack = stack[:len(stack)-1] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Add account URI to checked. | 
					
						
							|  |  |  | 		checked[next.URI] = struct{}{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Fetch any accounts that list 'next' as their 'moved_to_uri'. | 
					
						
							|  |  |  | 		movedFrom, err := p.state.DB.GetAccountsByMovedToURI(ctx, next.URI) | 
					
						
							|  |  |  | 		if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 			err := gtserror.Newf("error fetching accounts by moved_to_uri: %w", err) | 
					
						
							|  |  |  | 			return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for _, account := range movedFrom { | 
					
						
							|  |  |  | 			if _, ok := checked[account.URI]; ok { | 
					
						
							|  |  |  | 				// Account with URI has | 
					
						
							|  |  |  | 				// already been checked. | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Check movedFrom accounts to ensure | 
					
						
							|  |  |  | 			// none of them actually come from target, | 
					
						
							|  |  |  | 			// which would cause a recursion loop. | 
					
						
							|  |  |  | 			if account.URI == target.URI { | 
					
						
							|  |  |  | 				text := fmt.Sprintf("move %s -> %s would cause move recursion due to %s", origin.URI, target.URI, account.URI) | 
					
						
							|  |  |  | 				return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// Append 'from' account to stack. | 
					
						
							|  |  |  | 			stack = append(stack, account) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } |