summaryrefslogtreecommitdiff
path: root/internal/federation
diff options
context:
space:
mode:
authorLibravatar tobi <tobi.smethurst@protonmail.com>2025-10-15 18:57:57 +0200
committerLibravatar tobi <tobi.smethurst@protonmail.com>2025-10-17 15:33:49 +0200
commit6fee55dcff976f3eeae5879fe91d2f27780d0da4 (patch)
treed028c3ac30a84fc6095c9ca9dd4d136f905d8887 /internal/federation
parent[bugfix] Fix HTTP return code for Likes of remote statuses (#4504) (diff)
downloadgotosocial-6fee55dcff976f3eeae5879fe91d2f27780d0da4.tar.xz
[chore] Rationalize HTTP return codes for fedi endpoints, other tidying up (#4503)
# Description > If this is a code change, please include a summary of what you've coded, and link to the issue(s) it closes/implements. > > If this is a documentation change, please briefly describe what you've changed and why. This pull request does some refactoring of the fedi API endpoints and processing functions, and the authenticate + pub key deref functions, to try to return fewer silly HTTP codes like 410 Gone (when a *remote* account is gone, not a local one), and 500 errors where something isn't really an error. Also does some general tidying up and renaming for consistency. ## Checklist Please put an x inside each checkbox to indicate that you've read and followed it: `[ ]` -> `[x]` If this is a documentation change, only the first checkbox must be filled (you can delete the others if you want). - [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md). - [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat. - [x] I/we have not leveraged AI to create the proposed changes. - [x] I/we have performed a self-review of added code. - [x] I/we have written code that is legible and maintainable by others. - [x] I/we have commented the added code, particularly in hard-to-understand areas. - [ ] I/we have made any necessary changes to documentation. - [ ] I/we have added tests that cover new code. - [x] I/we have run tests and they pass locally with the changes. - [x] I/we have run `go fmt ./...` and `golangci-lint run`. Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4503 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
Diffstat (limited to 'internal/federation')
-rw-r--r--internal/federation/authenticate.go159
-rw-r--r--internal/federation/federatingactor.go26
-rw-r--r--internal/federation/federatingprotocol.go73
-rw-r--r--internal/federation/gone.go41
4 files changed, 184 insertions, 115 deletions
diff --git a/internal/federation/authenticate.go b/internal/federation/authenticate.go
index f8515e649..fabef822c 100644
--- a/internal/federation/authenticate.go
+++ b/internal/federation/authenticate.go
@@ -36,6 +36,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
+ "code.superseriousbusiness.org/gotosocial/internal/id"
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/httpsig"
"codeberg.org/gruf/go-kv/v2"
@@ -103,7 +104,10 @@ type PubKeyAuth struct {
//
// Note that it is also valid to pass in an empty string here, in which case the
// keys of the instance account will be used.
-func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedUsername string) (*PubKeyAuth, gtserror.WithCode) {
+//
+// The caller of this function MUST CHECK AT SOME POINT WHETHER THE PUB KEY OWNER
+// HAS BEEN SUSPENDED, and handle it appropriately, as this function will not do so!
+func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedUser string) (*PubKeyAuth, gtserror.WithCode) {
// Thanks to the signature check middleware,
// we should already have an http signature
// verifier set on the context. If we don't,
@@ -144,7 +148,7 @@ func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
l := log.
WithContext(ctx).
WithFields(kv.Fields{
- {"requestedUsername", requestedUsername},
+ {"requestedUser", requestedUser},
{"pubKeyID", pubKeyIDStr},
}...)
@@ -153,7 +157,7 @@ func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
pubKeyAuth, errWithCode = f.derefPubKeyDBOnly(ctx, pubKeyIDStr)
} else {
l.Trace("public key is remote, checking if we need to dereference")
- pubKeyAuth, errWithCode = f.derefPubKey(ctx, requestedUsername, pubKeyIDStr, pubKeyID)
+ pubKeyAuth, errWithCode = f.derefPubKey(ctx, requestedUser, pubKeyIDStr, pubKeyID)
}
if errWithCode != nil {
@@ -182,7 +186,7 @@ func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
// Ensure we have instance stored in
// database for the account at URI.
err := f.fetchAccountInstance(ctx,
- requestedUsername,
+ requestedUser,
pubKeyAuth.OwnerURI,
)
if err != nil {
@@ -192,7 +196,7 @@ func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
// If we're currently handshaking with another instance, return
// without derefing the owner, the only possible time we do this.
// This prevents deadlocks when GTS instances mutually deref.
- if f.Handshaking(requestedUsername, pubKeyAuth.OwnerURI) {
+ if f.Handshaking(requestedUser, pubKeyAuth.OwnerURI) {
log.Warnf(ctx, "network race during %s handshake", pubKeyAuth.OwnerURI)
pubKeyAuth.Handshaking = true
return pubKeyAuth, nil
@@ -201,22 +205,14 @@ func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
// Dereference the account located at owner URI.
// Use exact URI match, not URL match.
pubKeyAuth.Owner, _, err = f.GetAccountByURI(ctx,
- requestedUsername,
+ requestedUser,
pubKeyAuth.OwnerURI,
false,
)
if err != nil {
- if gtserror.StatusCode(err) == http.StatusGone {
- // This can happen here instead of the pubkey 'gone'
- // checks due to: the server sending account deletion
- // notifications out, we start processing, the request above
- // succeeds, and *then* the profile is removed and starts
- // returning 410 Gone, at which point _this_ request fails.
- return nil, gtserror.NewErrorGone(err)
- }
-
- err := gtserror.Newf("error dereferencing account %s: %w", pubKeyAuth.OwnerURI, err)
- return nil, gtserror.NewErrorInternalError(err)
+ // If we couldn't fetch the pub key owner either from
+ // the DB or remote, return appropriate error code.
+ return nil, keyOwnerFetchError(err, pubKeyAuth.OwnerURI)
}
// Catch a possible (but very rare) race condition where
@@ -231,12 +227,68 @@ func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
}
}
- if !pubKeyAuth.Owner.SuspendedAt.IsZero() {
- const text = "requesting account suspended"
- return nil, gtserror.NewErrorForbidden(errors.New(text))
+ return pubKeyAuth, nil
+}
+
+// keyOwnerFetchError checks the given error for the
+// attempt to fetch the given account URI, and wraps
+// the error appropriately based on what went wrong.
+func keyOwnerFetchError(
+ err error,
+ acctURI *url.URL,
+) gtserror.WithCode {
+ var newErr error
+
+ // Check if a status code was returned
+ // from the failed dereference attempt.
+ switch statusCode := gtserror.StatusCode(err); statusCode {
+
+ case http.StatusUnauthorized:
+ // If we got 401 Unauthorized from the remote,
+ // something likely went wrong with signature
+ // verification. In this case we should also
+ // return unauthorized, as we can't validate.
+ //
+ // Unlike with forbidden, we should warn log
+ // about this, as it may indicate some kind
+ // of key mismatch necessitating admin action.
+ newErr = gtserror.Newf(
+ "received 401 Unauthorized fetching pub key owner %s: %w",
+ acctURI, err,
+ )
+
+ case http.StatusForbidden:
+ // If we got 403 Forbidden from the remote,
+ // we're not allowed to see the account making
+ // the request. In this case we should just
+ // return unauthorized, as we can't validate.
+ newErr = gtserror.Newf(
+ "received 403 Forbidden fetching pub key owner %s: %w",
+ acctURI, err,
+ )
+
+ case http.StatusGone:
+ // This can happen here instead of the pubkey
+ // 'gone' checks due to: the server sending account
+ // deletion notifications out, we start processing,
+ // the request above succeeds, and *then* the profile
+ // is removed and starts returning 410 Gone, at
+ // which point _this_ request fails.
+ newErr = gtserror.Newf(
+ "received 410 Gone fetching pub key owner %s: %w",
+ acctURI, err,
+ )
+
+ default:
+ // Handle all other errors
+ // (server not available etc).
+ newErr = gtserror.Newf(
+ "could not dereference pub key owner %s: %w",
+ acctURI, err,
+ )
}
- return pubKeyAuth, nil
+ return gtserror.NewErrorUnauthorized(newErr)
}
// derefPubKeyDBOnly tries to dereference the given
@@ -286,7 +338,7 @@ func (f *Federator) derefPubKeyDBOnly(
// extracting the key.
func (f *Federator) derefPubKey(
ctx context.Context,
- requestedUsername string,
+ requestedUser string,
pubKeyIDStr string,
pubKeyID *url.URL,
) (
@@ -296,10 +348,27 @@ func (f *Federator) derefPubKey(
l := log.
WithContext(ctx).
WithFields(kv.Fields{
- {"requestedUsername", requestedUsername},
+ {"requestedUser", requestedUser},
{"pubKeyID", pubKeyIDStr},
}...)
+ // If we've tried to get this pub key before and we
+ // now have a tombstone for it (ie., it's been deleted
+ // from remote), don't try to dereference it again.
+ gone, err := f.db.TombstoneExistsWithURI(ctx, pubKeyIDStr)
+ if err != nil {
+ err := gtserror.Newf("error checking for tombstone (%s): %w", pubKeyIDStr, err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ if gone {
+ // If we had a tombstone for the remote, set an HTTP
+ // response code of 410 Gone on the returned errWithCode.
+ err := gtserror.Newf("account with public key %s is gone: %w", pubKeyID, err)
+ err = gtserror.WithStatusCode(err, http.StatusGone)
+ return nil, gtserror.NewErrorUnauthorized(err)
+ }
+
// Try a database only deref first. We may already
// have the requesting account cached locally.
pubKeyAuth, errWithCode := f.derefPubKeyDBOnly(ctx, pubKeyIDStr)
@@ -332,22 +401,8 @@ func (f *Federator) derefPubKey(
)
}
- // If we've tried to get this account before and we
- // now have a tombstone for it (ie., it's been deleted
- // from remote), don't try to dereference it again.
- gone, err := f.CheckGone(ctx, pubKeyID)
- if err != nil {
- err := gtserror.Newf("error checking for tombstone (%s): %w", pubKeyIDStr, err)
- return nil, gtserror.NewErrorInternalError(err)
- }
-
- if gone {
- err := gtserror.Newf("account with public key is gone (%s)", pubKeyIDStr)
- return nil, gtserror.NewErrorGone(err)
- }
-
// Make an http call to get the (refreshed) pubkey.
- pubKeyBytes, errWithCode := f.callForPubKey(ctx, requestedUsername, pubKeyID)
+ pubKeyBytes, errWithCode := f.callForPubKey(ctx, requestedUser, pubKeyID)
if errWithCode != nil {
return nil, errWithCode
}
@@ -399,10 +454,10 @@ func (f *Federator) derefPubKey(
// callForPubKey handles the nitty gritty of actually
// making a request for the given pubKeyID with a
-// transport created on behalf of requestedUsername.
+// transport created on behalf of requestedUser.
func (f *Federator) callForPubKey(
ctx context.Context,
- requestedUsername string,
+ requestedUser string,
pubKeyID *url.URL,
) ([]byte, gtserror.WithCode) {
// Use a transport to dereference the remote.
@@ -410,10 +465,10 @@ func (f *Federator) callForPubKey(
// We're on a hot path: don't retry if req fails.
gtscontext.SetFastFail(ctx),
- requestedUsername,
+ requestedUser,
)
if err != nil {
- err = gtserror.Newf("error creating transport for %s: %w", requestedUsername, err)
+ err = gtserror.Newf("error creating transport for %s: %w", requestedUser, err)
return nil, gtserror.NewErrorInternalError(err)
}
@@ -439,13 +494,21 @@ func (f *Federator) callForPubKey(
// (account deleted, moved, etc). Add a tombstone
// to our database so that we can avoid trying to
// dereference it in future.
- if err := f.HandleGone(ctx, pubKeyID); err != nil {
- err := gtserror.Newf("error marking public key %s as gone: %w", pubKeyID, err)
+ pubKeyIDStr := pubKeyID.String()
+ tombstone := &gtsmodel.Tombstone{
+ ID: id.NewULID(),
+ Domain: pubKeyID.Host,
+ URI: pubKeyIDStr,
+ }
+ if err := f.db.PutTombstone(ctx, tombstone); err != nil && !errors.Is(err, db.ErrAlreadyExists) {
+ err := gtserror.Newf("db error adding tombstone for pub key %s: %w", pubKeyIDStr, err)
return nil, gtserror.NewErrorInternalError(err)
}
- err := gtserror.Newf("account with public key %s is gone", pubKeyID)
- return nil, gtserror.NewErrorGone(err)
+ // Wrap the error to preserve the 410 Gone status code.
+ err := gtserror.Newf("account with public key %s is gone: %w", pubKeyID, err)
+ err = gtserror.WithStatusCode(err, http.StatusGone)
+ return nil, gtserror.NewErrorUnauthorized(err)
}
// Fall back to generic error.
@@ -457,7 +520,7 @@ func (f *Federator) callForPubKey(
// the database for the given account URI, deref'ing if necessary.
func (f *Federator) fetchAccountInstance(
ctx context.Context,
- requestedUsername string,
+ requestedUser string,
accountURI *url.URL,
) error {
// Look for an existing entry for instance in database.
@@ -475,7 +538,7 @@ func (f *Federator) fetchAccountInstance(
// instance yet; go dereference it.
instance, err = f.GetRemoteInstance(
gtscontext.SetFastFail(ctx),
- requestedUsername,
+ requestedUser,
&url.URL{
Scheme: accountURI.Scheme,
Host: accountURI.Host,
diff --git a/internal/federation/federatingactor.go b/internal/federation/federatingactor.go
index 2a68865aa..316835036 100644
--- a/internal/federation/federatingactor.go
+++ b/internal/federation/federatingactor.go
@@ -31,6 +31,7 @@ import (
apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/db"
+ "code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/log"
"code.superseriousbusiness.org/gotosocial/internal/uris"
@@ -162,6 +163,31 @@ func (f *federatingActor) PostInboxScheme(ctx context.Context, w http.ResponseWr
return false, gtserror.NewErrorUnauthorized(errors.New(text), text)
}
+ // Ensure requester is not suspended.
+ requester := gtscontext.RequestingAccount(ctx)
+ switch {
+ case !requester.IsSuspended():
+ // Account in good standing.
+ // Allow request to continue.
+
+ case requester.DeletedSelf():
+ // Looks like pub key owner deleted their own account.
+ // Likely their instance is still sending out deletes,
+ // but we'll have already deleted everything of theirs.
+ // Don't do any further processing of the request.
+ log.Debugf(ctx,
+ "requesting account %s self deleted, ignoring inbox post",
+ requester.UsernameDomain(),
+ )
+ return true, nil
+
+ default:
+ // Likely suspended by an admin or
+ // defed action on *this* instance.
+ const text = "requesting account suspended"
+ return false, gtserror.NewErrorForbidden(errors.New(text), text)
+ }
+
/*
Begin processing the request, but note that we
have not yet applied authorization (ie., blocks).
diff --git a/internal/federation/federatingprotocol.go b/internal/federation/federatingprotocol.go
index e1ec86b32..2f6953257 100644
--- a/internal/federation/federatingprotocol.go
+++ b/internal/federation/federatingprotocol.go
@@ -198,39 +198,56 @@ func (f *Federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr
// account by parsing username from `/users/{username}/inbox`.
username, err := uris.ParseInboxPath(r.URL)
if err != nil {
- err = gtserror.Newf("could not parse %s as inbox path: %w", r.URL.String(), err)
+ err := gtserror.Newf("could not parse %s as inbox path: %w", r.URL.String(), err)
return nil, false, err
}
if username == "" {
- err = gtserror.New("inbox username was empty")
+ err := gtserror.New("inbox username was empty")
return nil, false, err
}
- receivingAccount, err := f.db.GetAccountByUsernameDomain(ctx, username, "")
- if err != nil {
- err = gtserror.Newf("could not fetch receiving account %s: %w", username, err)
+ // Get the receiving local account inbox
+ // owner with given username from database.
+ receiver, err := f.db.GetAccountByUsernameDomain(ctx, username, "")
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ err := gtserror.Newf("db error getting receiving account %s: %w", username, err)
return nil, false, err
}
+ if receiver == nil {
+ // Maybe we had this account at some point and someone
+ // manually deleted it from the DB. Just return not found.
+ err := gtserror.Newf("receiving account %s not found in the db", username)
+ errWithCode := gtserror.NewErrorNotFound(err)
+ return ctx, false, errWithCode
+ }
+
// Check who's trying to deliver to us by inspecting the http signature.
- pubKeyAuth, errWithCode := f.AuthenticateFederatedRequest(ctx, receivingAccount.Username)
+ pubKeyAuth, errWithCode := f.AuthenticateFederatedRequest(ctx, receiver.Username)
if errWithCode != nil {
- switch errWithCode.Code() {
- case http.StatusUnauthorized, http.StatusForbidden, http.StatusBadRequest:
- // If codes 400, 401, or 403, obey the go-fed
- // interface by writing the header and bailing.
- w.WriteHeader(errWithCode.Code())
- case http.StatusGone:
- // If the requesting account's key has gone
- // (410) then likely inbox post was a delete.
+
+ // Check if we got an error code from a remote
+ // instance while trying to dereference the pub
+ // key owner who's trying to post to this inbox.
+ if gtserror.StatusCode(errWithCode) == http.StatusGone {
+ // If the pub key owner's key/account has gone
+ // (410) then likely inbox post was a Delete.
//
- // We can just write 202 and leave: we didn't
- // know about the account anyway, so we can't
- // do any further processing.
+ // If so, we can just write 202 and leave, as
+ // either we'll have already processed any Deletes
+ // sent by this account, or we never met the account
+ // in the first place so we don't have any of their
+ // stuff stored to actually delete.
w.WriteHeader(http.StatusAccepted)
+ return ctx, false, nil
}
+ // In all other cases, obey the go-fed
+ // interface by writing the status
+ // code from the returned ErrWithCode.
+ w.WriteHeader(errWithCode.Code())
+
// We still return the error
// for later request logging.
return ctx, false, errWithCode
@@ -247,7 +264,11 @@ func (f *Federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr
// We have everything we need now, set the requesting
// and receiving accounts on the context for later use.
ctx = gtscontext.SetRequestingAccount(ctx, pubKeyAuth.Owner)
- ctx = gtscontext.SetReceivingAccount(ctx, receivingAccount)
+ ctx = gtscontext.SetReceivingAccount(ctx, receiver)
+
+ // Note: we do not check here yet whether requesting
+ // account has been suspended or self-deleted, as that
+ // is handled in *federatingActor.PostInboxScheme
return ctx, true, nil
}
@@ -290,7 +311,7 @@ func (f *Federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, er
// then we can save some work.
blocked, err := f.db.AreURIsBlocked(ctx, actorIRIs)
if err != nil {
- err = gtserror.Newf("error checking domain blocks of actorIRIs: %w", err)
+ err := gtserror.Newf("error checking domain blocks of actorIRIs: %w", err)
return false, err
}
@@ -302,7 +323,7 @@ func (f *Federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, er
// Now user level blocks. Receiver should not block requester.
blocked, err = f.db.IsBlocked(ctx, receivingAccount.ID, requestingAccount.ID)
if err != nil {
- err = gtserror.Newf("db error checking block between receiver and requester: %w", err)
+ err := gtserror.Newf("db error checking block between receiver and requester: %w", err)
return false, err
}
@@ -358,7 +379,7 @@ func (f *Federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, er
)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
// Real db error.
- err = gtserror.Newf("db error trying to get %s as account: %w", iriStr, err)
+ err := gtserror.Newf("db error trying to get %s as account: %w", iriStr, err)
return false, err
} else if err == nil {
// IRI is for an account.
@@ -373,7 +394,7 @@ func (f *Federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, er
)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
// Real db error.
- err = gtserror.Newf("db error trying to get %s as status: %w", iriStr, err)
+ err := gtserror.Newf("db error trying to get %s as status: %w", iriStr, err)
return false, err
} else if err == nil {
// IRI is for a status.
@@ -395,9 +416,9 @@ func (f *Federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, er
// account they have blocked. In this case, it's v. unlikely
// they care to see the boost in their timeline, so there's
// no point in us processing it.
- blocked, err = f.db.IsBlocked(ctx, receivingAccount.ID, accountID)
+ blocked, err := f.db.IsBlocked(ctx, receivingAccount.ID, accountID)
if err != nil {
- err = gtserror.Newf("db error checking block between receiver and other account: %w", err)
+ err := gtserror.Newf("db error checking block between receiver and other account: %w", err)
return false, err
}
@@ -418,9 +439,9 @@ func (f *Federator) Blocked(ctx context.Context, actorIRIs []*url.URL) (bool, er
// accounts are gossiping about + trying to tag a third account
// who has one or the other of them blocked.
if iriHost == ourHost {
- blocked, err = f.db.IsBlocked(ctx, accountID, requestingAccount.ID)
+ blocked, err := f.db.IsBlocked(ctx, accountID, requestingAccount.ID)
if err != nil {
- err = gtserror.Newf("db error checking block between other account and requester: %w", err)
+ err := gtserror.Newf("db error checking block between other account and requester: %w", err)
return false, err
}
diff --git a/internal/federation/gone.go b/internal/federation/gone.go
deleted file mode 100644
index 932bd17a1..000000000
--- a/internal/federation/gone.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// 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 federation
-
-import (
- "context"
- "net/url"
-
- "code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
- "code.superseriousbusiness.org/gotosocial/internal/id"
-)
-
-// CheckGone checks if a tombstone exists in the database for AP Actor or Object with the given uri.
-func (f *Federator) CheckGone(ctx context.Context, uri *url.URL) (bool, error) {
- return f.db.TombstoneExistsWithURI(ctx, uri.String())
-}
-
-// HandleGone puts a tombstone in the database, which marks an AP Actor or Object with the given uri as gone.
-func (f *Federator) HandleGone(ctx context.Context, uri *url.URL) error {
- tombstone := &gtsmodel.Tombstone{
- ID: id.NewULID(),
- Domain: uri.Host,
- URI: uri.String(),
- }
- return f.db.PutTombstone(ctx, tombstone)
-}