summaryrefslogtreecommitdiff
path: root/internal/processing/fedi
diff options
context:
space:
mode:
Diffstat (limited to 'internal/processing/fedi')
-rw-r--r--internal/processing/fedi/accept.go57
-rw-r--r--internal/processing/fedi/authorization.go87
-rw-r--r--internal/processing/fedi/collections.go85
-rw-r--r--internal/processing/fedi/common.go107
-rw-r--r--internal/processing/fedi/emoji.go57
-rw-r--r--internal/processing/fedi/status.go36
-rw-r--r--internal/processing/fedi/user.go123
-rw-r--r--internal/processing/fedi/wellknown.go9
8 files changed, 296 insertions, 265 deletions
diff --git a/internal/processing/fedi/accept.go b/internal/processing/fedi/accept.go
deleted file mode 100644
index 97e36fbb3..000000000
--- a/internal/processing/fedi/accept.go
+++ /dev/null
@@ -1,57 +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 fedi
-
-import (
- "context"
-
- "code.superseriousbusiness.org/gotosocial/internal/ap"
- "code.superseriousbusiness.org/gotosocial/internal/gtserror"
-)
-
-// AcceptGet handles the getting of a fedi/activitypub
-// representation of a local interaction acceptance.
-//
-// It performs appropriate authentication before
-// returning a JSON serializable interface.
-func (p *Processor) AcceptGet(
- ctx context.Context,
- requestedUser string,
- intReqID string,
-) (any, gtserror.WithCode) {
- // Ensure valid request, intReq exists, etc.
- intReq, errWithCode := p.validateIntReqRequest(ctx, requestedUser, intReqID)
- if errWithCode != nil {
- return nil, errWithCode
- }
-
- // Convert + serialize the Accept.
- accept, err := p.converter.InteractionReqToASAccept(ctx, intReq)
- if err != nil {
- err := gtserror.Newf("error converting to accept: %w", err)
- return nil, gtserror.NewErrorInternalError(err)
- }
-
- data, err := ap.Serialize(accept)
- if err != nil {
- err := gtserror.Newf("error serializing accept: %w", err)
- return nil, gtserror.NewErrorInternalError(err)
- }
-
- return data, nil
-}
diff --git a/internal/processing/fedi/authorization.go b/internal/processing/fedi/authorization.go
index bbba6a2d8..276dd3a82 100644
--- a/internal/processing/fedi/authorization.go
+++ b/internal/processing/fedi/authorization.go
@@ -19,9 +19,13 @@ package fedi
import (
"context"
+ "errors"
+ "fmt"
"code.superseriousbusiness.org/gotosocial/internal/ap"
+ "code.superseriousbusiness.org/gotosocial/internal/db"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
+ "code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
)
// AuthorizationGet handles the getting of a fedi/activitypub
@@ -35,7 +39,7 @@ func (p *Processor) AuthorizationGet(
intReqID string,
) (any, gtserror.WithCode) {
// Ensure valid request, intReq exists, etc.
- intReq, errWithCode := p.validateIntReqRequest(ctx, requestedUser, intReqID)
+ intReq, errWithCode := p.validateAuthGetRequest(ctx, requestedUser, intReqID)
if errWithCode != nil {
return nil, errWithCode
}
@@ -55,3 +59,84 @@ func (p *Processor) AuthorizationGet(
return data, nil
}
+
+// AcceptGet handles the getting of a fedi/activitypub
+// representation of a local interaction acceptance.
+//
+// It performs appropriate authentication before
+// returning a JSON serializable interface.
+func (p *Processor) AcceptGet(
+ ctx context.Context,
+ requestedUser string,
+ intReqID string,
+) (any, gtserror.WithCode) {
+ // Ensure valid request, intReq exists, etc.
+ intReq, errWithCode := p.validateAuthGetRequest(ctx, requestedUser, intReqID)
+ if errWithCode != nil {
+ return nil, errWithCode
+ }
+
+ // Convert + serialize the Accept.
+ accept, err := p.converter.InteractionReqToASAccept(ctx, intReq)
+ if err != nil {
+ err := gtserror.Newf("error converting to accept: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ data, err := ap.Serialize(accept)
+ if err != nil {
+ err := gtserror.Newf("error serializing accept: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ return data, nil
+}
+
+// validateAuthGetRequest is a shortcut function
+// for returning an accepted interaction request
+// targeting `requestedUser`.
+func (p *Processor) validateAuthGetRequest(
+ ctx context.Context,
+ requestedUser string,
+ intReqID string,
+) (*gtsmodel.InteractionRequest, gtserror.WithCode) {
+ // Authenticate incoming request, getting related accounts.
+ auth, errWithCode := p.authenticate(ctx, requestedUser)
+ if errWithCode != nil {
+ return nil, errWithCode
+ }
+
+ if auth.handshakingURI != nil {
+ // We're currently handshaking, which means we don't know
+ // this account yet. This should be a very rare race condition.
+ err := gtserror.Newf("network race handshaking %s", auth.handshakingURI)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ // Fetch interaction request with the given ID.
+ req, err := p.state.DB.GetInteractionRequestByID(ctx, intReqID)
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ err := gtserror.Newf("db error getting interaction request %s: %w", intReqID, err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ // Ensure that this is an existing
+ // and *accepted* interaction request.
+ if req == nil || !req.IsAccepted() {
+ const text = "interaction request not found"
+ return nil, gtserror.NewErrorNotFound(errors.New(text))
+ }
+
+ // Ensure interaction request was accepted
+ // by the account in the request path.
+ if req.TargetAccountID != auth.receiver.ID {
+ text := fmt.Sprintf(
+ "account %s is not targeted by interaction request %s and therefore can't accept it",
+ requestedUser, intReqID,
+ )
+ return nil, gtserror.NewErrorNotFound(errors.New(text))
+ }
+
+ // All fine.
+ return req, nil
+}
diff --git a/internal/processing/fedi/collections.go b/internal/processing/fedi/collections.go
index ae4860b15..b67651dff 100644
--- a/internal/processing/fedi/collections.go
+++ b/internal/processing/fedi/collections.go
@@ -54,24 +54,24 @@ func (p *Processor) OutboxGet(
ctx context.Context,
requestedUser string,
page *paging.Page,
-) (interface{}, gtserror.WithCode) {
+) (any, gtserror.WithCode) {
// Authenticate incoming request, getting related accounts.
auth, errWithCode := p.authenticate(ctx, requestedUser)
if errWithCode != nil {
return nil, errWithCode
}
- receivingAcct := auth.receivingAcct
+ receiver := auth.receiver
// Parse the collection ID object from account's followers URI.
- collectionID, err := url.Parse(receivingAcct.OutboxURI)
+ collectionID, err := url.Parse(receiver.OutboxURI)
if err != nil {
- err := gtserror.Newf("error parsing account outbox uri %s: %w", receivingAcct.OutboxURI, err)
+ err := gtserror.Newf("error parsing account outbox uri %s: %w", receiver.OutboxURI, err)
return nil, gtserror.NewErrorInternalError(err)
}
// Ensure we have stats for this account.
- if err := p.state.DB.PopulateAccountStats(ctx, receivingAcct); err != nil {
- err := gtserror.Newf("error getting stats for account %s: %w", receivingAcct.ID, err)
+ if err := p.state.DB.PopulateAccountStats(ctx, receiver); err != nil {
+ err := gtserror.Newf("error getting stats for account %s: %w", receiver.ID, err)
return nil, gtserror.NewErrorInternalError(err)
}
@@ -83,8 +83,8 @@ func (p *Processor) OutboxGet(
switch {
- case receivingAcct.IsInstance() ||
- *receivingAcct.Settings.HideCollections:
+ case receiver.IsInstance() ||
+ *receiver.Settings.HideCollections:
// If account that hides collections, or instance
// account (ie., can't post / have relationships),
// just return barest stub of collection.
@@ -94,7 +94,7 @@ func (p *Processor) OutboxGet(
// If paging disabled, or we're currently handshaking
// the requester, just return collection that links
// to first page (i.e. path below), with no items.
- params.Total = util.Ptr(*receivingAcct.Stats.StatusesCount)
+ params.Total = util.Ptr(*receiver.Stats.StatusesCount)
params.First = new(paging.Page)
params.Query = make(url.Values, 1)
params.Query.Set("limit", "40") // enables paging
@@ -105,7 +105,7 @@ func (p *Processor) OutboxGet(
// Get page of full public statuses.
statuses, err := p.state.DB.GetAccountStatuses(
ctx,
- receivingAcct.ID,
+ receiver.ID,
page.GetLimit(), // limit
true, // excludeReplies
true, // excludeReblogs
@@ -133,7 +133,7 @@ func (p *Processor) OutboxGet(
// (eg., local-only statuses, if the requester is remote).
statuses, err = p.visFilter.StatusesVisible(
ctx,
- auth.requestingAcct,
+ auth.requester,
statuses,
)
if err != nil {
@@ -142,7 +142,7 @@ func (p *Processor) OutboxGet(
}
// Start building AS collection page params.
- params.Total = util.Ptr(*receivingAcct.Stats.StatusesCount)
+ params.Total = util.Ptr(*receiver.Stats.StatusesCount)
var pageParams ap.CollectionPageParams
pageParams.CollectionParams = params
@@ -194,24 +194,24 @@ func (p *Processor) FollowersGet(
ctx context.Context,
requestedUser string,
page *paging.Page,
-) (interface{}, gtserror.WithCode) {
+) (any, gtserror.WithCode) {
// Authenticate incoming request, getting related accounts.
auth, errWithCode := p.authenticate(ctx, requestedUser)
if errWithCode != nil {
return nil, errWithCode
}
- receivingAcct := auth.receivingAcct
+ receiver := auth.receiver
// Parse the collection ID object from account's followers URI.
- collectionID, err := url.Parse(receivingAcct.FollowersURI)
+ collectionID, err := url.Parse(receiver.FollowersURI)
if err != nil {
- err := gtserror.Newf("error parsing account followers uri %s: %w", receivingAcct.FollowersURI, err)
+ err := gtserror.Newf("error parsing account followers uri %s: %w", receiver.FollowersURI, err)
return nil, gtserror.NewErrorInternalError(err)
}
// Ensure we have stats for this account.
- if err := p.state.DB.PopulateAccountStats(ctx, receivingAcct); err != nil {
- err := gtserror.Newf("error getting stats for account %s: %w", receivingAcct.ID, err)
+ if err := p.state.DB.PopulateAccountStats(ctx, receiver); err != nil {
+ err := gtserror.Newf("error getting stats for account %s: %w", receiver.ID, err)
return nil, gtserror.NewErrorInternalError(err)
}
@@ -223,8 +223,8 @@ func (p *Processor) FollowersGet(
switch {
- case receivingAcct.IsInstance() ||
- *receivingAcct.Settings.HideCollections:
+ case receiver.IsInstance() ||
+ *receiver.Settings.HideCollections:
// If account that hides collections, or instance
// account (ie., can't post / have relationships),
// just return barest stub of collection.
@@ -234,7 +234,7 @@ func (p *Processor) FollowersGet(
// If paging disabled, or we're currently handshaking
// the requester, just return collection that links
// to first page (i.e. path below), with no items.
- params.Total = util.Ptr(*receivingAcct.Stats.FollowersCount)
+ params.Total = util.Ptr(*receiver.Stats.FollowersCount)
params.First = new(paging.Page)
params.Query = make(url.Values, 1)
params.Query.Set("limit", "40") // enables paging
@@ -243,7 +243,7 @@ func (p *Processor) FollowersGet(
default:
// Paging enabled.
// Get page of full follower objects with attached accounts.
- followers, err := p.state.DB.GetAccountFollowers(ctx, receivingAcct.ID, page)
+ followers, err := p.state.DB.GetAccountFollowers(ctx, receiver.ID, page)
if err != nil {
err := gtserror.Newf("error getting followers: %w", err)
return nil, gtserror.NewErrorInternalError(err)
@@ -260,7 +260,7 @@ func (p *Processor) FollowersGet(
}
// Start building AS collection page params.
- params.Total = util.Ptr(*receivingAcct.Stats.FollowersCount)
+ params.Total = util.Ptr(*receiver.Stats.FollowersCount)
var pageParams ap.CollectionPageParams
pageParams.CollectionParams = params
@@ -306,24 +306,24 @@ func (p *Processor) FollowersGet(
// FollowingGet returns the serialized ActivityPub
// collection of a local account's following collection,
// which contains links to accounts followed by this account.
-func (p *Processor) FollowingGet(ctx context.Context, requestedUser string, page *paging.Page) (interface{}, gtserror.WithCode) {
+func (p *Processor) FollowingGet(ctx context.Context, requestedUser string, page *paging.Page) (any, gtserror.WithCode) {
// Authenticate incoming request, getting related accounts.
auth, errWithCode := p.authenticate(ctx, requestedUser)
if errWithCode != nil {
return nil, errWithCode
}
- receivingAcct := auth.receivingAcct
+ receiver := auth.receiver
// Parse collection ID from account's following URI.
- collectionID, err := url.Parse(receivingAcct.FollowingURI)
+ collectionID, err := url.Parse(receiver.FollowingURI)
if err != nil {
- err := gtserror.Newf("error parsing account following uri %s: %w", receivingAcct.FollowingURI, err)
+ err := gtserror.Newf("error parsing account following uri %s: %w", receiver.FollowingURI, err)
return nil, gtserror.NewErrorInternalError(err)
}
// Ensure we have stats for this account.
- if err := p.state.DB.PopulateAccountStats(ctx, receivingAcct); err != nil {
- err := gtserror.Newf("error getting stats for account %s: %w", receivingAcct.ID, err)
+ if err := p.state.DB.PopulateAccountStats(ctx, receiver); err != nil {
+ err := gtserror.Newf("error getting stats for account %s: %w", receiver.ID, err)
return nil, gtserror.NewErrorInternalError(err)
}
@@ -334,8 +334,8 @@ func (p *Processor) FollowingGet(ctx context.Context, requestedUser string, page
params.ID = collectionID
switch {
- case receivingAcct.IsInstance() ||
- *receivingAcct.Settings.HideCollections:
+ case receiver.IsInstance() ||
+ *receiver.Settings.HideCollections:
// If account that hides collections, or instance
// account (ie., can't post / have relationships),
// just return barest stub of collection.
@@ -345,7 +345,7 @@ func (p *Processor) FollowingGet(ctx context.Context, requestedUser string, page
// If paging disabled, or we're currently handshaking
// the requester, just return collection that links
// to first page (i.e. path below), with no items.
- params.Total = util.Ptr(*receivingAcct.Stats.FollowingCount)
+ params.Total = util.Ptr(*receiver.Stats.FollowingCount)
params.First = new(paging.Page)
params.Query = make(url.Values, 1)
params.Query.Set("limit", "40") // enables paging
@@ -354,7 +354,7 @@ func (p *Processor) FollowingGet(ctx context.Context, requestedUser string, page
default:
// Paging enabled.
// Get page of full follower objects with attached accounts.
- follows, err := p.state.DB.GetAccountFollows(ctx, receivingAcct.ID, page)
+ follows, err := p.state.DB.GetAccountFollows(ctx, receiver.ID, page)
if err != nil {
err := gtserror.Newf("error getting follows: %w", err)
return nil, gtserror.NewErrorInternalError(err)
@@ -371,7 +371,7 @@ func (p *Processor) FollowingGet(ctx context.Context, requestedUser string, page
}
// Start AS collection page params.
- params.Total = util.Ptr(*receivingAcct.Stats.FollowingCount)
+ params.Total = util.Ptr(*receiver.Stats.FollowingCount)
var pageParams ap.CollectionPageParams
pageParams.CollectionParams = params
@@ -416,28 +416,29 @@ func (p *Processor) FollowingGet(ctx context.Context, requestedUser string, page
// FeaturedCollectionGet returns an ordered collection of the requested username's Pinned posts.
// The returned collection have an `items` property which contains an ordered list of status URIs.
-func (p *Processor) FeaturedCollectionGet(ctx context.Context, requestedUser string) (interface{}, gtserror.WithCode) {
+func (p *Processor) FeaturedCollectionGet(ctx context.Context, requestedUser string) (any, gtserror.WithCode) {
// Authenticate incoming request, getting related accounts.
auth, errWithCode := p.authenticate(ctx, requestedUser)
if errWithCode != nil {
return nil, errWithCode
}
- receivingAcct := auth.receivingAcct
+ receiver := auth.receiver
- statuses, err := p.state.DB.GetAccountPinnedStatuses(ctx, receivingAcct.ID)
- if err != nil {
- if !errors.Is(err, db.ErrNoEntries) {
- return nil, gtserror.NewErrorInternalError(err)
- }
+ statuses, err := p.state.DB.GetAccountPinnedStatuses(ctx, receiver.ID)
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ err := gtserror.Newf("db error getting pinned statuses: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
}
- collection, err := p.converter.StatusesToASFeaturedCollection(ctx, receivingAcct.FeaturedCollectionURI, statuses)
+ collection, err := p.converter.StatusesToASFeaturedCollection(ctx, receiver.FeaturedCollectionURI, statuses)
if err != nil {
+ err := gtserror.Newf("error converting pinned statuses: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
data, err := ap.Serialize(collection)
if err != nil {
+ err := gtserror.Newf("error serializing: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
diff --git a/internal/processing/fedi/common.go b/internal/processing/fedi/common.go
index fc783f93e..ff6ed6fd4 100644
--- a/internal/processing/fedi/common.go
+++ b/internal/processing/fedi/common.go
@@ -20,7 +20,6 @@ package fedi
import (
"context"
"errors"
- "fmt"
"net/url"
"code.superseriousbusiness.org/gotosocial/internal/db"
@@ -30,21 +29,23 @@ import (
type commonAuth struct {
handshakingURI *url.URL // Set to requestingAcct's URI if we're currently handshaking them.
- requestingAcct *gtsmodel.Account // Remote account making request to this instance.
- receivingAcct *gtsmodel.Account // Local account receiving the request.
+ requester *gtsmodel.Account // Remote account making request to this instance.
+ receiver *gtsmodel.Account // Local account receiving the request.
}
+// authenticate is a util function for authenticating a signed GET
+// request to one of the AP/fedi resources handled in this package.
func (p *Processor) authenticate(ctx context.Context, requestedUser string) (*commonAuth, gtserror.WithCode) {
- // First get the requested (receiving) LOCAL account with username from database.
+ // Get the requested local account
+ // with given username from database.
receiver, err := p.state.DB.GetAccountByUsernameDomain(ctx, requestedUser, "")
- if err != nil {
- if !errors.Is(err, db.ErrNoEntries) {
- // Real db error.
- err = gtserror.Newf("db error getting account %s: %w", requestedUser, err)
- return nil, gtserror.NewErrorInternalError(err)
- }
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ err = gtserror.Newf("db error getting account %s: %w", requestedUser, err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
- // Account just not found in the db.
+ if receiver == nil {
+ err := gtserror.Newf("account %s not found in the db", requestedUser)
return nil, gtserror.NewErrorNotFound(err)
}
@@ -60,74 +61,44 @@ func (p *Processor) authenticate(ctx context.Context, requestedUser string) (*co
// don't know the requester yet.
return &commonAuth{
handshakingURI: pubKeyAuth.OwnerURI,
- receivingAcct: receiver,
+ receiver: receiver,
}, nil
}
// Get requester from auth.
requester := pubKeyAuth.Owner
- // Ensure block does not exist between receiver and requester.
- blocked, err := p.state.DB.IsEitherBlocked(ctx, receiver.ID, requester.ID)
- if err != nil {
- err := gtserror.Newf("error checking block: %w", err)
- return nil, gtserror.NewErrorInternalError(err)
- } else if blocked {
- const text = "block exists between accounts"
- return nil, gtserror.NewErrorForbidden(errors.New(text))
+ // Check if requester is suspended.
+ switch {
+ case !requester.IsSuspended():
+ // No problem.
+
+ case requester.DeletedSelf():
+ // Requester deleted their own account.
+ // Why are they now requesting something?
+ err := gtserror.Newf("requester %s self-deleted", requester.UsernameDomain())
+ return nil, gtserror.NewErrorUnauthorized(err)
+
+ default:
+ // Admin from our instance likely suspended account.
+ err := gtserror.Newf("requester %s is suspended", requester.UsernameDomain())
+ return nil, gtserror.NewErrorForbidden(err)
}
- return &commonAuth{
- requestingAcct: requester,
- receivingAcct: receiver,
- }, nil
-}
-
-// validateIntReqRequest is a shortcut function
-// for returning an accepted interaction request
-// targeting `requestedUser`.
-func (p *Processor) validateIntReqRequest(
- ctx context.Context,
- requestedUser string,
- intReqID string,
-) (*gtsmodel.InteractionRequest, gtserror.WithCode) {
- // Authenticate incoming request, getting related accounts.
- auth, errWithCode := p.authenticate(ctx, requestedUser)
- if errWithCode != nil {
- return nil, errWithCode
- }
-
- if auth.handshakingURI != nil {
- // We're currently handshaking, which means we don't know
- // this account yet. This should be a very rare race condition.
- err := gtserror.Newf("network race handshaking %s", auth.handshakingURI)
- return nil, gtserror.NewErrorInternalError(err)
- }
-
- // Fetch interaction request with the given ID.
- req, err := p.state.DB.GetInteractionRequestByID(ctx, intReqID)
- if err != nil && !errors.Is(err, db.ErrNoEntries) {
- err := gtserror.Newf("db error getting interaction request %s: %w", intReqID, err)
+ // Ensure receiver does not block requester.
+ blocked, err := p.state.DB.IsBlocked(ctx, receiver.ID, requester.ID)
+ if err != nil {
+ err := gtserror.Newf("db error checking block: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
- // Ensure that this is an existing
- // and *accepted* interaction request.
- if req == nil || !req.IsAccepted() {
- const text = "interaction request not found"
- return nil, gtserror.NewErrorNotFound(errors.New(text))
- }
-
- // Ensure interaction request was accepted
- // by the account in the request path.
- if req.TargetAccountID != auth.receivingAcct.ID {
- text := fmt.Sprintf(
- "account %s is not targeted by interaction request %s and therefore can't accept it",
- requestedUser, intReqID,
- )
- return nil, gtserror.NewErrorNotFound(errors.New(text))
+ if blocked {
+ var text = requestedUser + " blocks " + requester.Username
+ return nil, gtserror.NewErrorForbidden(errors.New(text))
}
- // All fine.
- return req, nil
+ return &commonAuth{
+ requester: requester,
+ receiver: receiver,
+ }, nil
}
diff --git a/internal/processing/fedi/emoji.go b/internal/processing/fedi/emoji.go
index 8db8b48ea..e7e3ec406 100644
--- a/internal/processing/fedi/emoji.go
+++ b/internal/processing/fedi/emoji.go
@@ -19,38 +19,69 @@ package fedi
import (
"context"
- "fmt"
+ "errors"
"code.superseriousbusiness.org/gotosocial/internal/ap"
+ "code.superseriousbusiness.org/gotosocial/internal/config"
+ "code.superseriousbusiness.org/gotosocial/internal/db"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
)
-// EmojiGet handles the GET for a federated emoji originating from this instance.
-func (p *Processor) EmojiGet(ctx context.Context, requestedEmojiID string) (interface{}, gtserror.WithCode) {
- if _, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, ""); errWithCode != nil {
+// EmojiGet handles the GET for an emoji originating from this instance.
+func (p *Processor) EmojiGet(ctx context.Context, emojiID string) (any, gtserror.WithCode) {
+ // Authenticate incoming request.
+ //
+ // Pass hostname string to this function to indicate
+ // it's the instance account being requested, as
+ // emojis are always owned by the instance account.
+ auth, errWithCode := p.authenticate(ctx, config.GetHost())
+ if errWithCode != nil {
return nil, errWithCode
}
- requestedEmoji, err := p.state.DB.GetEmojiByID(ctx, requestedEmojiID)
- if err != nil {
- return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting emoji with id %s: %s", requestedEmojiID, err))
+ if auth.handshakingURI != nil {
+ // We're currently handshaking, which means
+ // we don't know this account yet. This should
+ // be a very rare race condition.
+ err := gtserror.Newf("network race handshaking %s", auth.handshakingURI)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ // Get the requested emoji.
+ emoji, err := p.state.DB.GetEmojiByID(ctx, emojiID)
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ err := gtserror.Newf("db error getting emoji %s: %w", emojiID, err)
+ return nil, gtserror.NewErrorNotFound(err)
}
- if !requestedEmoji.IsLocal() {
- return nil, gtserror.NewErrorNotFound(fmt.Errorf("emoji with id %s doesn't belong to this instance (domain %s)", requestedEmojiID, requestedEmoji.Domain))
+ if emoji == nil {
+ err := gtserror.Newf("emoji %s not found in the db", emojiID)
+ return nil, gtserror.NewErrorNotFound(err)
}
- if *requestedEmoji.Disabled {
- return nil, gtserror.NewErrorNotFound(fmt.Errorf("emoji with id %s has been disabled", requestedEmojiID))
+ // Only serve *our*
+ // emojis on this path.
+ if !emoji.IsLocal() {
+ err := gtserror.Newf("emoji %s doesn't belong to this instance (domain is %s)", emojiID, emoji.Domain)
+ return nil, gtserror.NewErrorNotFound(err)
}
- apEmoji, err := p.converter.EmojiToAS(ctx, requestedEmoji)
+ // Don't serve emojis that have
+ // been disabled by an admin.
+ if *emoji.Disabled {
+ err := gtserror.Newf("emoji with id %s has been disabled by an admin", emojiID)
+ return nil, gtserror.NewErrorNotFound(err)
+ }
+
+ apEmoji, err := p.converter.EmojiToAS(ctx, emoji)
if err != nil {
- return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting gtsmodel emoji with id %s to ap emoji: %s", requestedEmojiID, err))
+ err := gtserror.Newf("error converting emoji %s to ap: %s", emojiID, err)
+ return nil, gtserror.NewErrorInternalError(err)
}
data, err := ap.Serialize(apEmoji)
if err != nil {
+ err := gtserror.Newf("error serializing emoji %s: %w", emojiID, err)
return nil, gtserror.NewErrorInternalError(err)
}
diff --git a/internal/processing/fedi/status.go b/internal/processing/fedi/status.go
index 497bcc177..d1de6f4c1 100644
--- a/internal/processing/fedi/status.go
+++ b/internal/processing/fedi/status.go
@@ -26,6 +26,7 @@ import (
"code.superseriousbusiness.org/activity/streams/vocab"
"code.superseriousbusiness.org/gotosocial/internal/ap"
+ "code.superseriousbusiness.org/gotosocial/internal/db"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"code.superseriousbusiness.org/gotosocial/internal/log"
@@ -33,9 +34,13 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/util"
)
-// StatusGet handles the getting of a fedi/activitypub representation of a local status.
+// StatusGet handles getting an AP representation of a local status.
// It performs appropriate authentication before returning a JSON serializable interface.
-func (p *Processor) StatusGet(ctx context.Context, requestedUser string, statusID string) (interface{}, gtserror.WithCode) {
+func (p *Processor) StatusGet(
+ ctx context.Context,
+ requestedUser string,
+ statusID string,
+) (any, gtserror.WithCode) {
// Authenticate incoming request, getting related accounts.
auth, errWithCode := p.authenticate(ctx, requestedUser)
if errWithCode != nil {
@@ -49,16 +54,23 @@ func (p *Processor) StatusGet(ctx context.Context, requestedUser string, statusI
err := gtserror.Newf("network race handshaking %s", auth.handshakingURI)
return nil, gtserror.NewErrorInternalError(err)
}
-
- receivingAcct := auth.receivingAcct
- requestingAcct := auth.requestingAcct
+ receiver := auth.receiver
+ requester := auth.requester
status, err := p.state.DB.GetStatusByID(ctx, statusID)
- if err != nil {
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ err := gtserror.Newf("db error getting status: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
+ }
+
+ if status == nil {
+ // TODO: Update this to serve "gone"
+ // when a status has been deleted.
+ err := gtserror.Newf("status %s not found in the db", statusID)
return nil, gtserror.NewErrorNotFound(err)
}
- if status.AccountID != receivingAcct.ID {
+ if status.AccountID != receiver.ID {
const text = "status does not belong to receiving account"
return nil, gtserror.NewErrorNotFound(errors.New(text))
}
@@ -68,7 +80,7 @@ func (p *Processor) StatusGet(ctx context.Context, requestedUser string, statusI
return nil, gtserror.NewErrorNotFound(errors.New(text))
}
- visible, err := p.visFilter.StatusVisible(ctx, requestingAcct, status)
+ visible, err := p.visFilter.StatusVisible(ctx, requester, status)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@@ -93,7 +105,7 @@ func (p *Processor) StatusGet(ctx context.Context, requestedUser string, statusI
return data, nil
}
-// GetStatus handles the getting of a fedi/activitypub representation of replies to a status,
+// GetStatus handles getting an AP representation of replies to a status,
// performing appropriate authentication before returning a JSON serializable interface to the caller.
func (p *Processor) StatusRepliesGet(
ctx context.Context,
@@ -101,7 +113,7 @@ func (p *Processor) StatusRepliesGet(
statusID string,
page *paging.Page,
onlyOtherAccounts bool,
-) (interface{}, gtserror.WithCode) {
+) (any, gtserror.WithCode) {
// Authenticate incoming request, getting related accounts.
auth, errWithCode := p.authenticate(ctx, requestedUser)
if errWithCode != nil {
@@ -116,8 +128,8 @@ func (p *Processor) StatusRepliesGet(
return nil, gtserror.NewErrorInternalError(err)
}
- receivingAcct := auth.receivingAcct
- requestingAcct := auth.requestingAcct
+ receivingAcct := auth.receiver
+ requestingAcct := auth.requester
// Get target status and ensure visible to requester.
status, errWithCode := p.c.GetVisibleTargetStatus(ctx,
diff --git a/internal/processing/fedi/user.go b/internal/processing/fedi/user.go
index 53dfd6022..9fb338673 100644
--- a/internal/processing/fedi/user.go
+++ b/internal/processing/fedi/user.go
@@ -20,96 +20,83 @@ package fedi
import (
"context"
"errors"
- "fmt"
- "net/url"
"code.superseriousbusiness.org/gotosocial/internal/ap"
"code.superseriousbusiness.org/gotosocial/internal/db"
+ "code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
- "code.superseriousbusiness.org/gotosocial/internal/uris"
)
-// UserGet handles the getting of a fedi/activitypub representation of a user/account,
-// performing authentication before returning a JSON serializable interface to the caller.
-func (p *Processor) UserGet(ctx context.Context, requestedUsername string, requestURL *url.URL) (interface{}, gtserror.WithCode) {
- // (Try to) get the requested local account from the db.
- receiver, err := p.state.DB.GetAccountByUsernameDomain(ctx, requestedUsername, "")
- if err != nil {
- if errors.Is(err, db.ErrNoEntries) {
- // Account just not found w/ this username.
- err := fmt.Errorf("account with username %s not found in the db", requestedUsername)
- return nil, gtserror.NewErrorNotFound(err)
- }
+// UserGet handles getting an AP representation of an account.
+// It does auth before returning a JSON serializable interface to the caller.
+func (p *Processor) UserGet(
+ ctx context.Context,
+ requestedUser string,
+) (any, gtserror.WithCode) {
+ // Authenticate incoming request, getting related accounts.
+ //
+ // We may currently be handshaking with the remote account
+ // making the request. Unlike with other fedi endpoints,
+ // don't bother checking this; if we're still handshaking
+ // just serve the AP representation of our account anyway.
+ //
+ // This ensures that we don't get stuck in a loop with another
+ // GtS instance, where each instance is trying repeatedly to
+ // dereference the other account that's making the request
+ // before it will reveal its own account.
+ //
+ // Instead, we end up in an 'I'll show you mine if you show me
+ // yours' situation, where we sort of agree to reveal each
+ // other's profiles at the same time.
+ auth, errWithCode := p.authenticate(ctx, requestedUser)
+ if errWithCode != nil {
+ return nil, errWithCode
+ }
- // Real db error.
- err := fmt.Errorf("db error getting account with username %s: %w", requestedUsername, err)
+ // Generate the proper AP representation.
+ accountable, err := p.converter.AccountToAS(ctx, auth.receiver)
+ if err != nil {
+ err := gtserror.Newf("error converting to accountable: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
- if uris.IsPublicKeyPath(requestURL) {
- // If request is on a public key path, we don't need to
- // authenticate this request. However, we'll only serve
- // the bare minimum user profile needed for the pubkey.
- //
- // TODO: https://codeberg.org/superseriousbusiness/gotosocial/issues/1186
- minimalPerson, err := p.converter.AccountToASMinimal(ctx, receiver)
- if err != nil {
- err := gtserror.Newf("error converting to minimal account: %w", err)
- return nil, gtserror.NewErrorInternalError(err)
- }
-
- // Return early with bare minimum data.
- return data(minimalPerson)
+ data, err := ap.Serialize(accountable)
+ if err != nil {
+ err := gtserror.Newf("error serializing accountable: %w", err)
+ return nil, gtserror.NewErrorInternalError(err)
}
- // If the request is not on a public key path, we want to
- // try to authenticate it before we serve any data, so that
- // we can serve a more complete profile.
- pubKeyAuth, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, requestedUsername)
- if errWithCode != nil {
- return nil, errWithCode // likely 401
- }
+ return data, nil
+}
- // Auth passed, generate the proper AP representation.
- accountable, err := p.converter.AccountToAS(ctx, receiver)
- if err != nil {
- err := gtserror.Newf("error converting account: %w", err)
+// UserGetMinimal returns a minimal AP representation
+// of the requested account, containing just the public
+// key, without doing authentication.
+func (p *Processor) UserGetMinimal(
+ ctx context.Context,
+ requestedUser string,
+) (any, gtserror.WithCode) {
+ acct, err := p.state.DB.GetAccountByUsernameDomain(
+ gtscontext.SetBarebones(ctx),
+ requestedUser, "",
+ )
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ err := gtserror.Newf("db error getting account %s: %w", requestedUser, err)
return nil, gtserror.NewErrorInternalError(err)
}
- if pubKeyAuth.Handshaking {
- // If we are currently handshaking with the remote account
- // making the request, then don't be coy: just serve the AP
- // representation of the target account.
- //
- // This handshake check ensures that we don't get stuck in
- // a loop with another GtS instance, where each instance is
- // trying repeatedly to dereference the other account that's
- // making the request before it will reveal its own account.
- //
- // Instead, we end up in an 'I'll show you mine if you show me
- // yours' situation, where we sort of agree to reveal each
- // other's profiles at the same time.
- return data(accountable)
+ if acct == nil {
+ err := gtserror.Newf("account %s not found in the db", requestedUser)
+ return nil, gtserror.NewErrorNotFound(err)
}
- // Get requester from auth.
- requester := pubKeyAuth.Owner
-
- // Check that block does not exist between receiver and requester.
- blocked, err := p.state.DB.IsBlocked(ctx, receiver.ID, requester.ID)
+ // Generate minimal AP representation.
+ accountable, err := p.converter.AccountToASMinimal(ctx, acct)
if err != nil {
- err := gtserror.Newf("error checking block: %w", err)
+ err := gtserror.Newf("error converting to accountable: %w", err)
return nil, gtserror.NewErrorInternalError(err)
- } else if blocked {
- const text = "block exists between accounts"
- return nil, gtserror.NewErrorForbidden(errors.New(text))
}
- return data(accountable)
-}
-
-func data(accountable ap.Accountable) (interface{}, gtserror.WithCode) {
data, err := ap.Serialize(accountable)
if err != nil {
err := gtserror.Newf("error serializing accountable: %w", err)
diff --git a/internal/processing/fedi/wellknown.go b/internal/processing/fedi/wellknown.go
index 0e6989803..236f09257 100644
--- a/internal/processing/fedi/wellknown.go
+++ b/internal/processing/fedi/wellknown.go
@@ -47,7 +47,7 @@ var (
nodeInfoProtocols = []string{"activitypub"}
nodeInfoInbound = []string{}
nodeInfoOutbound = []string{}
- nodeInfoMetadata = make(map[string]interface{})
+ nodeInfoMetadata = make(map[string]any)
)
// NodeInfoRelGet returns a well known response giving the path to node info.
@@ -156,11 +156,12 @@ func (p *Processor) HostMetaGet() *apimodel.HostMeta {
}
// WebfingerGet handles the GET for a webfinger resource. Most commonly, it will be used for returning account lookups.
-func (p *Processor) WebfingerGet(ctx context.Context, requestedUsername string) (*apimodel.WellKnownResponse, gtserror.WithCode) {
+func (p *Processor) WebfingerGet(ctx context.Context, requestedUser string) (*apimodel.WellKnownResponse, gtserror.WithCode) {
// Get the local account the request is referring to.
- requestedAccount, err := p.state.DB.GetAccountByUsernameDomain(ctx, requestedUsername, "")
+ requestedAccount, err := p.state.DB.GetAccountByUsernameDomain(ctx, requestedUser, "")
if err != nil {
- return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting account with username %s: %s", requestedUsername, err))
+ err := gtserror.Newf("db error getting account %s: %s", requestedUser, err)
+ return nil, gtserror.NewErrorNotFound(err)
}
return &apimodel.WellKnownResponse{