fiddle with it! (that's what she said)

This commit is contained in:
tobi 2025-04-06 12:02:26 +02:00
commit 1bfc1aa7d1
10 changed files with 218 additions and 199 deletions

View file

@ -46,22 +46,48 @@ type accountDB struct {
state *state.State state *state.State
} }
func (a *accountDB) getAccountsBy( func (a *accountDB) GetAccountByID(ctx context.Context, id string) (*gtsmodel.Account, error) {
ctx context.Context, return a.getAccount(
index string, ctx,
keys []string, "ID",
load func([]string) ([]*gtsmodel.Account, error), func(account *gtsmodel.Account) error {
getKey func(*gtsmodel.Account) string, return a.db.NewSelect().
) ([]*gtsmodel.Account, error) { Model(account).
// Load all input account keys via cache loader callback. Where("? = ?", bun.Ident("account.id"), id).
accounts, err := a.state.Caches.DB.Account.LoadIDs(index, keys, load) Scan(ctx)
},
id,
)
}
func (a *accountDB) GetAccountsByIDs(ctx context.Context, ids []string) ([]*gtsmodel.Account, error) {
// Load all input account IDs via cache loader callback.
accounts, err := a.state.Caches.DB.Account.LoadIDs("ID",
ids,
func(uncached []string) ([]*gtsmodel.Account, error) {
// Preallocate expected length of uncached accounts.
accounts := make([]*gtsmodel.Account, 0, len(uncached))
// Perform database query scanning
// the remaining (uncached) account IDs.
if err := a.db.NewSelect().
Model(&accounts).
Where("? IN (?)", bun.Ident("id"), bun.In(uncached)).
Scan(ctx); err != nil {
return nil, err
}
return accounts, nil
},
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Reorder the statuses by their // Reorder the statuses by their
// keys to ensure in correct order. // IDs to ensure in correct order.
xslices.OrderBy(accounts, keys, getKey) getID := func(a *gtsmodel.Account) string { return a.ID }
xslices.OrderBy(accounts, ids, getID)
if gtscontext.Barebones(ctx) { if gtscontext.Barebones(ctx) {
// no need to fully populate. // no need to fully populate.
@ -81,75 +107,6 @@ func (a *accountDB) getAccountsBy(
return accounts, nil return accounts, nil
} }
func (a *accountDB) getOneAccountBy(
ctx context.Context,
index string,
key string,
load func([]string) ([]*gtsmodel.Account, error),
) (*gtsmodel.Account, error) {
// Get all accounts with the given key.
accounts, err := a.getAccountsBy(
ctx,
index,
[]string{key},
load,
func(a *gtsmodel.Account) string { return key },
)
if err != nil {
return nil, err
}
// Ensure we have one
// and only one account.
l := len(accounts)
if l == 0 {
return nil, db.ErrNoEntries
}
if l > 1 {
return nil, db.ErrMultipleEntries
}
return accounts[0], nil
}
func (a *accountDB) GetAccountByID(ctx context.Context, id string) (*gtsmodel.Account, error) {
return a.getAccount(
ctx,
"ID",
func(account *gtsmodel.Account) error {
return a.db.NewSelect().
Model(account).
Where("? = ?", bun.Ident("account.id"), id).
Scan(ctx)
},
id,
)
}
func (a *accountDB) GetAccountsByIDs(ctx context.Context, ids []string) ([]*gtsmodel.Account, error) {
return a.getAccountsBy(
ctx,
"ID",
ids,
func(uncached []string) ([]*gtsmodel.Account, error) {
// Preallocate expected length of uncached accounts.
accounts := make([]*gtsmodel.Account, 0, len(uncached))
// Perform database query scanning
// the remaining (uncached) accounts.
if err := a.db.NewSelect().
Model(&accounts).
Where("? IN (?)", bun.Ident("account.id"), bun.In(uncached)).
Scan(ctx); err != nil {
return nil, err
}
return accounts, nil
},
func(a *gtsmodel.Account) string { return a.ID },
)
}
func (a *accountDB) GetAccountByURI(ctx context.Context, uri string) (*gtsmodel.Account, error) { func (a *accountDB) GetAccountByURI(ctx context.Context, uri string) (*gtsmodel.Account, error) {
return a.getAccount( return a.getAccount(
ctx, ctx,
@ -165,51 +122,45 @@ func (a *accountDB) GetAccountByURI(ctx context.Context, uri string) (*gtsmodel.
} }
func (a *accountDB) GetOneAccountByURL(ctx context.Context, url string) (*gtsmodel.Account, error) { func (a *accountDB) GetOneAccountByURL(ctx context.Context, url string) (*gtsmodel.Account, error) {
return a.getOneAccountBy( // Select IDs of all
ctx, // accounts with this url.
"URL", var ids []string
url, if err := a.db.NewSelect().
func(uncached []string) ([]*gtsmodel.Account, error) { TableExpr("? AS ?", bun.Ident("accounts"), bun.Ident("account")).
// Preallocate expected length of uncached accounts. Column("account.id").
accounts := make([]*gtsmodel.Account, 0, len(uncached)) Where("? = ?", bun.Ident("account.url"), url).
Scan(ctx, &ids); err != nil {
return nil, err
}
// Perform database query scanning // Ensure exactly one account.
// the remaining (uncached) accounts. if len(ids) == 0 {
if err := a.db.NewSelect(). return nil, db.ErrNoEntries
Model(&accounts). }
Where("? IN (?)", bun.Ident("account.url"), bun.In(uncached)). if len(ids) > 1 {
Scan(ctx); err != nil { return nil, db.ErrMultipleEntries
return nil, err }
}
return accounts, nil return a.GetAccountByID(ctx, ids[0])
},
)
} }
func (a *accountDB) GetAccountsByURL(ctx context.Context, url string) ([]*gtsmodel.Account, error) { func (a *accountDB) GetAccountsByURL(ctx context.Context, url string) ([]*gtsmodel.Account, error) {
// Get all accounts with the given URL. // Select IDs of all
return a.getAccountsBy( // accounts with this url.
ctx, var ids []string
"ID", if err := a.db.NewSelect().
[]string{url}, TableExpr("? AS ?", bun.Ident("accounts"), bun.Ident("account")).
func(uncached []string) ([]*gtsmodel.Account, error) { Column("account.id").
// Preallocate expected length of uncached accounts. Where("? = ?", bun.Ident("account.url"), url).
accounts := make([]*gtsmodel.Account, 0, len(uncached)) Scan(ctx, &ids); err != nil {
return nil, err
}
// Perform database query scanning if len(ids) == 0 {
// the remaining (uncached) accounts. return nil, db.ErrNoEntries
if err := a.db.NewSelect(). }
Model(&accounts).
Where("? IN (?)", bun.Ident("account.url"), bun.In(uncached)).
Scan(ctx); err != nil {
return nil, err
}
return accounts, nil return a.GetAccountsByIDs(ctx, ids)
},
func(a *gtsmodel.Account) string { return a.URL },
)
} }
func (a *accountDB) GetAccountByUsernameDomain(ctx context.Context, username string, domain string) (*gtsmodel.Account, error) { func (a *accountDB) GetAccountByUsernameDomain(ctx context.Context, username string, domain string) (*gtsmodel.Account, error) {
@ -261,50 +212,50 @@ func (a *accountDB) GetAccountByPubkeyID(ctx context.Context, id string) (*gtsmo
) )
} }
func (a *accountDB) GetOneAccountByInboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error) { func (a *accountDB) GetOneAccountByInboxURI(ctx context.Context, inboxURI string) (*gtsmodel.Account, error) {
return a.getOneAccountBy( // Select IDs of all accounts
ctx, // with this inbox_uri.
"InboxURI", var ids []string
uri, if err := a.db.NewSelect().
func(uncached []string) ([]*gtsmodel.Account, error) { TableExpr("? AS ?", bun.Ident("accounts"), bun.Ident("account")).
// Preallocate expected length of uncached accounts. Column("account.id").
accounts := make([]*gtsmodel.Account, 0, len(uncached)) Where("? = ?", bun.Ident("account.inbox_uri"), inboxURI).
Scan(ctx, &ids); err != nil {
return nil, err
}
// Perform database query scanning // Ensure exactly one account.
// the remaining (uncached) accounts. if len(ids) == 0 {
if err := a.db.NewSelect(). return nil, db.ErrNoEntries
Model(&accounts). }
Where("? IN (?)", bun.Ident("account.inbox_uri"), bun.In(uncached)). if len(ids) > 1 {
Scan(ctx); err != nil { return nil, db.ErrMultipleEntries
return nil, err }
}
return accounts, nil return a.GetAccountByID(ctx, ids[0])
},
)
} }
func (a *accountDB) GetOneAccountByOutboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error) { func (a *accountDB) GetOneAccountByOutboxURI(ctx context.Context, outboxURI string) (*gtsmodel.Account, error) {
return a.getOneAccountBy( // Select IDs of all accounts
ctx, // with this outbox_uri.
"OutboxURI", var ids []string
uri, if err := a.db.NewSelect().
func(uncached []string) ([]*gtsmodel.Account, error) { TableExpr("? AS ?", bun.Ident("accounts"), bun.Ident("account")).
// Preallocate expected length of uncached accounts. Column("account.id").
accounts := make([]*gtsmodel.Account, 0, len(uncached)) Where("? = ?", bun.Ident("account.outbox_uri"), outboxURI).
Scan(ctx, &ids); err != nil {
return nil, err
}
// Perform database query scanning // Ensure exactly one account.
// the remaining (uncached) accounts. if len(ids) == 0 {
if err := a.db.NewSelect(). return nil, db.ErrNoEntries
Model(&accounts). }
Where("? IN (?)", bun.Ident("account.outbox_uri"), bun.In(uncached)). if len(ids) > 1 {
Scan(ctx); err != nil { return nil, db.ErrMultipleEntries
return nil, err }
}
return accounts, nil return a.GetAccountByID(ctx, ids[0])
},
)
} }
func (a *accountDB) GetInstanceAccount(ctx context.Context, domain string) (*gtsmodel.Account, error) { func (a *accountDB) GetInstanceAccount(ctx context.Context, domain string) (*gtsmodel.Account, error) {

View file

@ -199,9 +199,11 @@ func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
} }
// Dereference the account located at owner URI. // Dereference the account located at owner URI.
// Use exact URI match, not URL match.
pubKeyAuth.Owner, _, err = f.GetAccountByURI(ctx, pubKeyAuth.Owner, _, err = f.GetAccountByURI(ctx,
requestedUsername, requestedUsername,
pubKeyAuth.OwnerURI, pubKeyAuth.OwnerURI,
false,
) )
if err != nil { if err != nil {
if gtserror.StatusCode(err) == http.StatusGone { if gtserror.StatusCode(err) == http.StatusGone {

View file

@ -89,14 +89,30 @@ func accountFresh(
return !time.Now().After(staleAt) return !time.Now().After(staleAt)
} }
// GetAccountByURI will attempt to fetch an accounts by its URI, first checking the database. In the case of a newly-met remote model, or a remote model // GetAccountByURI will attempt to fetch an accounts by its
// whose last_fetched date is beyond a certain interval, the account will be dereferenced. In the case of dereferencing, some low-priority account information // URI, first checking the database. In the case of a newly-met
// may be enqueued for asynchronous fetching, e.g. featured account statuses (pins). An ActivityPub object indicates the account was dereferenced. // remote model, or a remote model whose last_fetched date is
func (d *Dereferencer) GetAccountByURI(ctx context.Context, requestUser string, uri *url.URL) (*gtsmodel.Account, ap.Accountable, error) { // beyond a certain interval, the account will be dereferenced.
// In the case of dereferencing, some low-priority account info
// may be enqueued for asynchronous fetching, e.g. pinned statuses.
// An ActivityPub object indicates the account was dereferenced.
//
// if tryURL is true, then the database will also check for a *single*
// account where uri == account.url, not just uri == account.uri.
// Because url does not guarantee uniqueness, you should only set
// tryURL to true when doing searches set in motion by a user,
// ie., when it's not important that an exact account is returned.
func (d *Dereferencer) GetAccountByURI(
ctx context.Context,
requestUser string,
uri *url.URL,
tryURL bool,
) (*gtsmodel.Account, ap.Accountable, error) {
// Fetch and dereference account if necessary. // Fetch and dereference account if necessary.
account, accountable, err := d.getAccountByURI(ctx, account, accountable, err := d.getAccountByURI(ctx,
requestUser, requestUser,
uri, uri,
tryURL,
) )
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -118,8 +134,15 @@ func (d *Dereferencer) GetAccountByURI(ctx context.Context, requestUser string,
return account, accountable, nil return account, accountable, nil
} }
// getAccountByURI is a package internal form of .GetAccountByURI() that doesn't bother dereferencing featured posts on update. // getAccountByURI is a package internal form of
func (d *Dereferencer) getAccountByURI(ctx context.Context, requestUser string, uri *url.URL) (*gtsmodel.Account, ap.Accountable, error) { // .GetAccountByURI() that doesn't bother dereferencing
// featured posts on update.
func (d *Dereferencer) getAccountByURI(
ctx context.Context,
requestUser string,
uri *url.URL,
tryURL bool,
) (*gtsmodel.Account, ap.Accountable, error) {
var ( var (
account *gtsmodel.Account account *gtsmodel.Account
uriStr = uri.String() uriStr = uri.String()
@ -127,9 +150,8 @@ func (d *Dereferencer) getAccountByURI(ctx context.Context, requestUser string,
) )
// Search the database for existing account with URI. // Search the database for existing account with URI.
// URI is unique so if we get a hit it's that account for sure.
account, err = d.state.DB.GetAccountByURI( account, err = d.state.DB.GetAccountByURI(
// request a barebones object, it may be in the
// db but with related models not yet dereferenced.
gtscontext.SetBarebones(ctx), gtscontext.SetBarebones(ctx),
uriStr, uriStr,
) )
@ -137,9 +159,11 @@ func (d *Dereferencer) getAccountByURI(ctx context.Context, requestUser string,
return nil, nil, gtserror.Newf("error checking database for account %s by uri: %w", uriStr, err) return nil, nil, gtserror.Newf("error checking database for account %s by uri: %w", uriStr, err)
} }
if account == nil { if account == nil && tryURL {
// Else, search the database for one account with URL. // Else if we're permitted, search the database for *ONE*
// Can return multiple hits so check for ErrMultipleEntries. // account with this URL. This can return multiple hits
// so check for ErrMultipleEntries. If we get exactly one
// hit it's *probably* the account we're looking for.
account, err = d.state.DB.GetOneAccountByURL( account, err = d.state.DB.GetOneAccountByURL(
gtscontext.SetBarebones(ctx), gtscontext.SetBarebones(ctx),
uriStr, uriStr,

View file

@ -54,6 +54,7 @@ func (suite *AccountTestSuite) TestDereferenceGroup() {
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
groupURL, groupURL,
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(group) suite.NotNil(group)
@ -78,6 +79,7 @@ func (suite *AccountTestSuite) TestDereferenceService() {
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
serviceURL, serviceURL,
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(service) suite.NotNil(service)
@ -110,6 +112,7 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountAsRemoteURL() {
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
testrig.URLMustParse(targetAccount.URI), testrig.URLMustParse(targetAccount.URI),
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(fetchedAccount) suite.NotNil(fetchedAccount)
@ -129,6 +132,7 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountAsRemoteURLNoSharedInb
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
testrig.URLMustParse(targetAccount.URI), testrig.URLMustParse(targetAccount.URI),
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(fetchedAccount) suite.NotNil(fetchedAccount)
@ -143,6 +147,7 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountAsUsername() {
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
testrig.URLMustParse(targetAccount.URI), testrig.URLMustParse(targetAccount.URI),
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(fetchedAccount) suite.NotNil(fetchedAccount)
@ -157,6 +162,7 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountAsUsernameDomain() {
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
testrig.URLMustParse(targetAccount.URI), testrig.URLMustParse(targetAccount.URI),
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(fetchedAccount) suite.NotNil(fetchedAccount)
@ -213,6 +219,7 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountWithUnknownUserURI() {
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
testrig.URLMustParse("http://localhost:8080/users/thisaccountdoesnotexist"), testrig.URLMustParse("http://localhost:8080/users/thisaccountdoesnotexist"),
false,
) )
suite.True(gtserror.IsUnretrievable(err)) suite.True(gtserror.IsUnretrievable(err))
suite.EqualError(err, db.ErrNoEntries.Error()) suite.EqualError(err, db.ErrNoEntries.Error())
@ -265,7 +272,7 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountByRedirect() {
uri := testrig.URLMustParse("https://this-will-be-redirected.butts/") uri := testrig.URLMustParse("https://this-will-be-redirected.butts/")
// Try dereference the test URI, since it correctly redirects to us it should return our account. // Try dereference the test URI, since it correctly redirects to us it should return our account.
account, accountable, err := suite.dereferencer.GetAccountByURI(ctx, fetchingAccount.Username, uri) account, accountable, err := suite.dereferencer.GetAccountByURI(ctx, fetchingAccount.Username, uri, false)
suite.NoError(err) suite.NoError(err)
suite.Nil(accountable) suite.Nil(accountable)
suite.NotNil(account) suite.NotNil(account)
@ -318,7 +325,7 @@ func (suite *AccountTestSuite) TestDereferenceMasqueradingLocalAccount() {
) )
// Try dereference the test URI, since it correctly redirects to us it should return our account. // Try dereference the test URI, since it correctly redirects to us it should return our account.
account, accountable, err := suite.dereferencer.GetAccountByURI(ctx, fetchingAccount.Username, uri) account, accountable, err := suite.dereferencer.GetAccountByURI(ctx, fetchingAccount.Username, uri, false)
suite.NotNil(err) suite.NotNil(err)
suite.Nil(account) suite.Nil(account)
suite.Nil(accountable) suite.Nil(accountable)
@ -341,6 +348,7 @@ func (suite *AccountTestSuite) TestDereferenceRemoteAccountWithNonMatchingURI()
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
testrig.URLMustParse(remoteAltURI), testrig.URLMustParse(remoteAltURI),
false,
) )
suite.Equal(err.Error(), fmt.Sprintf("enrichAccount: account uri %s does not match %s", remoteURI, remoteAltURI)) suite.Equal(err.Error(), fmt.Sprintf("enrichAccount: account uri %s does not match %s", remoteURI, remoteAltURI))
suite.Nil(fetchedAccount) suite.Nil(fetchedAccount)
@ -357,6 +365,7 @@ func (suite *AccountTestSuite) TestDereferenceRemoteAccountWithUnexpectedKeyChan
remoteAcc, _, err := suite.dereferencer.GetAccountByURI(ctx, remoteAcc, _, err := suite.dereferencer.GetAccountByURI(ctx,
fetchingAcc.Username, fetchingAcc.Username,
testrig.URLMustParse(remoteURI), testrig.URLMustParse(remoteURI),
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(remoteAcc) suite.NotNil(remoteAcc)
@ -395,6 +404,7 @@ func (suite *AccountTestSuite) TestDereferenceRemoteAccountWithExpectedKeyChange
remoteAcc, _, err := suite.dereferencer.GetAccountByURI(ctx, remoteAcc, _, err := suite.dereferencer.GetAccountByURI(ctx,
fetchingAcc.Username, fetchingAcc.Username,
testrig.URLMustParse(remoteURI), testrig.URLMustParse(remoteURI),
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(remoteAcc) suite.NotNil(remoteAcc)
@ -436,6 +446,7 @@ func (suite *AccountTestSuite) TestRefreshFederatedRemoteAccountWithKeyChange()
remoteAcc, _, err := suite.dereferencer.GetAccountByURI(ctx, remoteAcc, _, err := suite.dereferencer.GetAccountByURI(ctx,
fetchingAcc.Username, fetchingAcc.Username,
testrig.URLMustParse(remoteURI), testrig.URLMustParse(remoteURI),
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(remoteAcc) suite.NotNil(remoteAcc)

View file

@ -454,7 +454,8 @@ func (d *Dereferencer) enrichStatus(
// Ensure we have the author account of the status dereferenced (+ up-to-date). If this is a new status // Ensure we have the author account of the status dereferenced (+ up-to-date). If this is a new status
// (i.e. status.AccountID == "") then any error here is irrecoverable. status.AccountID must ALWAYS be set. // (i.e. status.AccountID == "") then any error here is irrecoverable. status.AccountID must ALWAYS be set.
if _, _, err := d.getAccountByURI(ctx, requestUser, attributedTo); err != nil && status.AccountID == "" { // We want the exact URI match here as well, not the imprecise URL match.
if _, _, err := d.getAccountByURI(ctx, requestUser, attributedTo, false); err != nil && status.AccountID == "" {
// Note that we specifically DO NOT wrap the error, instead collapsing it as string. // Note that we specifically DO NOT wrap the error, instead collapsing it as string.
// Errors fetching an account do not necessarily relate to dereferencing the status. // Errors fetching an account do not necessarily relate to dereferencing the status.
@ -1312,35 +1313,17 @@ func (d *Dereferencer) getPopulatedMention(
bool, // True if mention already exists in the DB. bool, // True if mention already exists in the DB.
error, error,
) { ) {
// Mentions can be created using Name or Href. // Mentions can be created using `name` or `href`,
// Prefer Href (TargetAccountURI), fall back to Name. // and when mention was extracted, we ensured that
if mention.TargetAccountURI != "" { // one of either `name` or `href` was set.
//
// Prefer to deref the mention target using `name`,
// which should always be exact. and fall back to `href`,
// which *should* be the target's URI, but may be the URL
// depending on implementation.
if mention.NameString != "" {
// Look for existing mention with target account's URI, if so use this. // Extract the username and domain parts from namestring.
existingMention, ok := existing.GetMentionByTargetURI(mention.TargetAccountURI)
if ok && existingMention.ID != "" {
return existingMention, true, nil
}
// Ensure that mention account URI is parseable.
accountURI, err := url.Parse(mention.TargetAccountURI)
if err != nil {
err := gtserror.Newf("invalid account uri %q: %w", mention.TargetAccountURI, err)
return nil, false, err
}
// Ensure we have account of the mention target dereferenced.
mention.TargetAccount, _, err = d.getAccountByURI(ctx,
requestUser,
accountURI,
)
if err != nil {
err := gtserror.Newf("failed to dereference account %s: %w", accountURI, err)
return nil, false, err
}
} else {
// Href wasn't set, extract the username and domain parts from namestring.
username, domain, err := util.ExtractNamestringParts(mention.NameString) username, domain, err := util.ExtractNamestringParts(mention.NameString)
if err != nil { if err != nil {
err := gtserror.Newf("failed to parse namestring %s: %w", mention.NameString, err) err := gtserror.Newf("failed to parse namestring %s: %w", mention.NameString, err)
@ -1369,6 +1352,34 @@ func (d *Dereferencer) getPopulatedMention(
if ok && existingMention.ID != "" { if ok && existingMention.ID != "" {
return existingMention, true, nil return existingMention, true, nil
} }
} else {
// Name wasn't set.
//
// Look for existing mention with target account's URI, if so use this.
existingMention, ok := existing.GetMentionByTargetURI(mention.TargetAccountURI)
if ok && existingMention.ID != "" {
return existingMention, true, nil
}
// Ensure that mention account URI is parseable.
accountURI, err := url.Parse(mention.TargetAccountURI)
if err != nil {
err := gtserror.Newf("invalid account uri %q: %w", mention.TargetAccountURI, err)
return nil, false, err
}
// Ensure we have account of the mention target dereferenced.
// Don't allow imprecise URL here, we want the exact acct.
mention.TargetAccount, _, err = d.getAccountByURI(ctx,
requestUser,
accountURI,
false,
)
if err != nil {
err := gtserror.Newf("failed to dereference account %s: %w", accountURI, err)
return nil, false, err
}
} }
// At this point, mention.TargetAccountURI // At this point, mention.TargetAccountURI

View file

@ -107,9 +107,14 @@ func (p *Processor) Alias(
} }
// Ensure we have account dereferenced. // Ensure we have account dereferenced.
//
// As this comes from user input, allow checking
// by URL to make things easier, not just to an
// exact AP URI (which a user might not even know).
targetAccount, _, err := p.federator.GetAccountByURI(ctx, targetAccount, _, err := p.federator.GetAccountByURI(ctx,
account.Username, account.Username,
newAKA.uri, newAKA.uri,
true,
) )
if err != nil { if err != nil {
err := fmt.Errorf( err := fmt.Errorf(

View file

@ -66,10 +66,13 @@ func (p *Processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account
// Perform a last-minute fetch of target account to // Perform a last-minute fetch of target account to
// ensure remote account header / avatar is cached. // ensure remote account header / avatar is cached.
//
// Match by URI only.
latest, _, err := p.federator.GetAccountByURI( latest, _, err := p.federator.GetAccountByURI(
gtscontext.SetFastFail(ctx), gtscontext.SetFastFail(ctx),
requestingAccount.Username, requestingAccount.Username,
targetAccountURI, targetAccountURI,
false,
) )
if err != nil { if err != nil {
log.Errorf(ctx, "error fetching latest target account: %v", err) log.Errorf(ctx, "error fetching latest target account: %v", err)

View file

@ -119,11 +119,15 @@ func (p *Processor) MoveSelf(
unlock := p.state.ProcessingLocks.Lock(lockKey) unlock := p.state.ProcessingLocks.Lock(lockKey)
defer unlock() defer unlock()
// Ensure we have a valid, up-to-date representation of the target account. // Ensure we have a valid, up-to-date
// representation of the target account.
//
// Match by uri only.
targetAcct, targetAcctable, err = p.federator.GetAccountByURI( targetAcct, targetAcctable, err = p.federator.GetAccountByURI(
ctx, ctx,
originAcct.Username, originAcct.Username,
targetAcctURI, targetAcctURI,
false,
) )
if err != nil { if err != nil {
const text = "error dereferencing moved_to_uri" const text = "error dereferencing moved_to_uri"

View file

@ -564,10 +564,15 @@ func (p *Processor) accountsByURI(
if resolve { if resolve {
// We're allowed to resolve, leave the // We're allowed to resolve, leave the
// rest up to the dereferencer functions. // rest up to the dereferencer functions.
//
// Allow dereferencing by URL and not just URI;
// there are many cases where someone might
// paste a URL into the search bar.
account, _, err := p.federator.GetAccountByURI( account, _, err := p.federator.GetAccountByURI(
gtscontext.SetFastFail(ctx), gtscontext.SetFastFail(ctx),
requestingAccount.Username, requestingAccount.Username,
uri, uri,
true,
) )
return []*gtsmodel.Account{account}, err return []*gtsmodel.Account{account}, err

View file

@ -303,10 +303,13 @@ func (p *fediAPI) MoveAccount(ctx context.Context, fMsg *messages.FromFediAPI) e
} }
// Account to which the Move is taking place. // Account to which the Move is taking place.
//
// Match by uri only.
targetAcct, targetAcctable, err := p.federate.GetAccountByURI( targetAcct, targetAcctable, err := p.federate.GetAccountByURI(
ctx, ctx,
fMsg.Receiving.Username, fMsg.Receiving.Username,
targetAcctURI, targetAcctURI,
false,
) )
if err != nil { if err != nil {
return gtserror.Newf( return gtserror.Newf(