mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-28 16:22:24 -05:00
start refactoring return codes from fedi endpoints, remove some cruft
This commit is contained in:
parent
c6044d0142
commit
47051a26d6
28 changed files with 346 additions and 291 deletions
|
|
@ -20,16 +20,12 @@ package emoji
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/processing"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
// EmojiIDKey is for emoji IDs
|
||||
EmojiIDKey = "id"
|
||||
// EmojiBasePath is the base path for serving AP Emojis, minus the "emoji" prefix
|
||||
EmojiWithIDPath = "/:" + EmojiIDKey
|
||||
)
|
||||
const EmojiWithIDPath = "/:" + apiutil.IDKey
|
||||
|
||||
type Module struct {
|
||||
processor *processing.Processor
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ import (
|
|||
)
|
||||
|
||||
func (m *Module) EmojiGetHandler(c *gin.Context) {
|
||||
requestedEmojiID := strings.ToUpper(c.Param(EmojiIDKey))
|
||||
if requestedEmojiID == "" {
|
||||
emojiID := strings.ToUpper(c.Param(apiutil.IDKey))
|
||||
if emojiID == "" {
|
||||
err := errors.New("no emoji id specified in request")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
|
|
@ -41,7 +41,7 @@ func (m *Module) EmojiGetHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
resp, errWithCode := m.processor.Fedi().EmojiGet(c.Request.Context(), requestedEmojiID)
|
||||
resp, errWithCode := m.processor.Fedi().EmojiGet(c.Request.Context(), emojiID)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import (
|
|||
|
||||
"code.superseriousbusiness.org/gotosocial/internal/admin"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/api/activitypub/emoji"
|
||||
apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/db"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/email"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/federation"
|
||||
|
|
@ -122,7 +123,7 @@ func (suite *EmojiGetTestSuite) TestGetEmoji() {
|
|||
// but because we're calling the function directly, we need to set them manually.
|
||||
ctx.Params = gin.Params{
|
||||
gin.Param{
|
||||
Key: emoji.EmojiIDKey,
|
||||
Key: apiutil.IDKey,
|
||||
Value: targetEmoji.ID,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,17 +20,14 @@ package publickey
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util"
|
||||
|
||||
"code.superseriousbusiness.org/gotosocial/internal/processing"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/uris"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
// UsernameKey is for account usernames.
|
||||
UsernameKey = "username"
|
||||
// PublicKeyPath is a path to a user's public key, for serving bare minimum AP representations.
|
||||
PublicKeyPath = "users/:" + UsernameKey + "/" + uris.PublicKeyPath
|
||||
)
|
||||
const PublicKeyPath = "users/:" + apiutil.UsernameKey + "/" + uris.PublicKeyPath
|
||||
|
||||
type Module struct {
|
||||
processor *processing.Processor
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import (
|
|||
// public key, username, and type of the account.
|
||||
func (m *Module) PublicKeyGETHandler(c *gin.Context) {
|
||||
// usernames on our instance are always lowercase
|
||||
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||
requestedUsername := strings.ToLower(c.Param(apiutil.UsernameKey))
|
||||
if requestedUsername == "" {
|
||||
err := errors.New("no username specified in request")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
|
|
@ -47,13 +47,17 @@ func (m *Module) PublicKeyGETHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// If HTML is requested, redirect
|
||||
// to user's profile instead.
|
||||
if contentType == string(apiutil.TextHTML) {
|
||||
// redirect to the user's profile
|
||||
c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)
|
||||
return
|
||||
}
|
||||
|
||||
resp, errWithCode := m.processor.Fedi().UserGet(c.Request.Context(), requestedUsername, c.Request.URL)
|
||||
resp, errWithCode := m.processor.Fedi().UserGetMinimal(
|
||||
c.Request.Context(),
|
||||
requestedUsername,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ type SwaggerCollection struct {
|
|||
// A string or an array of strings, or more
|
||||
// complex nested items.
|
||||
// example: https://www.w3.org/ns/activitystreams
|
||||
Context interface{} `json:"@context"`
|
||||
Context any `json:"@context"`
|
||||
// ActivityStreams ID.
|
||||
// example: https://example.org/users/some_user/statuses/106717595988259568/replies
|
||||
ID string `json:"id"`
|
||||
|
|
@ -64,7 +64,7 @@ type SwaggerFeaturedCollection struct {
|
|||
// A string or an array of strings, or more
|
||||
// complex nested items.
|
||||
// example: https://www.w3.org/ns/activitystreams
|
||||
Context interface{} `json:"@context"`
|
||||
Context any `json:"@context"`
|
||||
// ActivityStreams ID.
|
||||
// example: https://example.org/users/some_user/collections/featured
|
||||
ID string `json:"id"`
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ import (
|
|||
// description: not found
|
||||
func (m *Module) FeaturedCollectionGETHandler(c *gin.Context) {
|
||||
// usernames on our instance are always lowercase
|
||||
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||
requestedUsername := strings.ToLower(c.Param(apiutil.UsernameKey))
|
||||
if requestedUsername == "" {
|
||||
err := errors.New("no username specified in request")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import (
|
|||
// FollowersGETHandler returns a collection of URIs for followers of the target user, formatted so that other AP servers can understand it.
|
||||
func (m *Module) FollowersGETHandler(c *gin.Context) {
|
||||
// usernames on our instance are always lowercase
|
||||
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||
requestedUsername := strings.ToLower(c.Param(apiutil.UsernameKey))
|
||||
if requestedUsername == "" {
|
||||
err := errors.New("no username specified in request")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import (
|
|||
// FollowingGETHandler returns a collection of URIs for accounts that the target user follows, formatted so that other AP servers can understand it.
|
||||
func (m *Module) FollowingGETHandler(c *gin.Context) {
|
||||
// usernames on our instance are always lowercase
|
||||
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||
requestedUsername := strings.ToLower(c.Param(apiutil.UsernameKey))
|
||||
if requestedUsername == "" {
|
||||
err := errors.New("no username specified in request")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import (
|
|||
"code.superseriousbusiness.org/activity/streams"
|
||||
"code.superseriousbusiness.org/activity/streams/vocab"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/ap"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/api/activitypub/users"
|
||||
apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/db"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
|
||||
|
|
@ -79,7 +79,7 @@ func (suite *InboxPostTestSuite) inboxPost(
|
|||
)
|
||||
|
||||
// Put the request together.
|
||||
ctx.AddParam(users.UsernameKey, targetAccount.Username)
|
||||
ctx.AddParam(apiutil.UsernameKey, targetAccount.Username)
|
||||
ctx.Request = httptest.NewRequest(http.MethodPost, targetAccount.InboxURI, bytes.NewReader(b))
|
||||
ctx.Request.Header.Set("Signature", signature)
|
||||
ctx.Request.Header.Set("Date", dateHeader)
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ import (
|
|||
// description: not found
|
||||
func (m *Module) OutboxGETHandler(c *gin.Context) {
|
||||
// usernames on our instance are always lowercase
|
||||
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||
requestedUsername := strings.ToLower(c.Param(apiutil.UsernameKey))
|
||||
if requestedUsername == "" {
|
||||
err := errors.New("no username specified in request")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import (
|
|||
|
||||
"code.superseriousbusiness.org/activity/streams"
|
||||
"code.superseriousbusiness.org/activity/streams/vocab"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/api/activitypub/users"
|
||||
apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util"
|
||||
"code.superseriousbusiness.org/gotosocial/testrig"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
|
@ -59,7 +59,7 @@ func (suite *OutboxGetTestSuite) TestGetOutbox() {
|
|||
// but because we're calling the function directly, we need to set them manually.
|
||||
ctx.Params = gin.Params{
|
||||
gin.Param{
|
||||
Key: users.UsernameKey,
|
||||
Key: apiutil.UsernameKey,
|
||||
Value: targetAccount.Username,
|
||||
},
|
||||
}
|
||||
|
|
@ -85,7 +85,7 @@ func (suite *OutboxGetTestSuite) TestGetOutbox() {
|
|||
"type": "OrderedCollection"
|
||||
}`, dst.String())
|
||||
|
||||
m := make(map[string]interface{})
|
||||
m := make(map[string]any)
|
||||
err = json.Unmarshal(b, &m)
|
||||
suite.NoError(err)
|
||||
|
||||
|
|
@ -117,7 +117,7 @@ func (suite *OutboxGetTestSuite) TestGetOutboxFirstPage() {
|
|||
// but because we're calling the function directly, we need to set them manually.
|
||||
ctx.Params = gin.Params{
|
||||
gin.Param{
|
||||
Key: users.UsernameKey,
|
||||
Key: apiutil.UsernameKey,
|
||||
Value: targetAccount.Username,
|
||||
},
|
||||
}
|
||||
|
|
@ -172,7 +172,7 @@ func (suite *OutboxGetTestSuite) TestGetOutboxFirstPage() {
|
|||
"type": "OrderedCollectionPage"
|
||||
}`, dst.String())
|
||||
|
||||
m := make(map[string]interface{})
|
||||
m := make(map[string]any)
|
||||
err = json.Unmarshal(b, &m)
|
||||
suite.NoError(err)
|
||||
|
||||
|
|
@ -204,11 +204,11 @@ func (suite *OutboxGetTestSuite) TestGetOutboxNextPage() {
|
|||
// but because we're calling the function directly, we need to set them manually.
|
||||
ctx.Params = gin.Params{
|
||||
gin.Param{
|
||||
Key: users.UsernameKey,
|
||||
Key: apiutil.UsernameKey,
|
||||
Value: targetAccount.Username,
|
||||
},
|
||||
gin.Param{
|
||||
Key: users.MaxIDKey,
|
||||
Key: apiutil.MaxIDKey,
|
||||
Value: "01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
},
|
||||
}
|
||||
|
|
@ -235,7 +235,7 @@ func (suite *OutboxGetTestSuite) TestGetOutboxNextPage() {
|
|||
"type": "OrderedCollectionPage"
|
||||
}`, dst.String())
|
||||
|
||||
m := make(map[string]interface{})
|
||||
m := make(map[string]any)
|
||||
err = json.Unmarshal(b, &m)
|
||||
suite.NoError(err)
|
||||
|
||||
|
|
@ -261,7 +261,7 @@ func checkDropPublished(t *testing.T, b []byte, at ...string) []byte {
|
|||
entries := make([]map[string]any, 0)
|
||||
for _, key := range at {
|
||||
switch vt := m[key].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
for _, t := range vt {
|
||||
if entry, ok := t.(map[string]any); ok {
|
||||
entries = append(entries, entry)
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ import (
|
|||
// description: not found
|
||||
func (m *Module) StatusRepliesGETHandler(c *gin.Context) {
|
||||
// usernames on our instance are always lowercase
|
||||
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||
requestedUsername := strings.ToLower(c.Param(apiutil.UsernameKey))
|
||||
if requestedUsername == "" {
|
||||
err := errors.New("no username specified in request")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
|
|
@ -99,7 +99,7 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
// status IDs on our instance are always uppercase
|
||||
requestedStatusID := strings.ToUpper(c.Param(StatusIDKey))
|
||||
requestedStatusID := strings.ToUpper(c.Param(apiutil.IDKey))
|
||||
if requestedStatusID == "" {
|
||||
err := errors.New("no status id specified in request")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import (
|
|||
"code.superseriousbusiness.org/activity/streams"
|
||||
"code.superseriousbusiness.org/activity/streams/vocab"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/ap"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/api/activitypub/users"
|
||||
apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util"
|
||||
"code.superseriousbusiness.org/gotosocial/testrig"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -61,11 +61,11 @@ func (suite *RepliesGetTestSuite) TestGetReplies() {
|
|||
// but because we're calling the function directly, we need to set them manually.
|
||||
ctx.Params = gin.Params{
|
||||
gin.Param{
|
||||
Key: users.UsernameKey,
|
||||
Key: apiutil.UsernameKey,
|
||||
Value: targetAccount.Username,
|
||||
},
|
||||
gin.Param{
|
||||
Key: users.StatusIDKey,
|
||||
Key: apiutil.IDKey,
|
||||
Value: targetStatus.ID,
|
||||
},
|
||||
}
|
||||
|
|
@ -96,7 +96,7 @@ func (suite *RepliesGetTestSuite) TestGetReplies() {
|
|||
})
|
||||
assert.Equal(suite.T(), expect, string(b))
|
||||
|
||||
m := make(map[string]interface{})
|
||||
m := make(map[string]any)
|
||||
err = json.Unmarshal(b, &m)
|
||||
assert.NoError(suite.T(), err)
|
||||
|
||||
|
|
@ -129,11 +129,11 @@ func (suite *RepliesGetTestSuite) TestGetRepliesNext() {
|
|||
// but because we're calling the function directly, we need to set them manually.
|
||||
ctx.Params = gin.Params{
|
||||
gin.Param{
|
||||
Key: users.UsernameKey,
|
||||
Key: apiutil.UsernameKey,
|
||||
Value: targetAccount.Username,
|
||||
},
|
||||
gin.Param{
|
||||
Key: users.StatusIDKey,
|
||||
Key: apiutil.IDKey,
|
||||
Value: targetStatus.ID,
|
||||
},
|
||||
}
|
||||
|
|
@ -167,7 +167,7 @@ func (suite *RepliesGetTestSuite) TestGetRepliesNext() {
|
|||
})
|
||||
assert.Equal(suite.T(), expect, string(b))
|
||||
|
||||
m := make(map[string]interface{})
|
||||
m := make(map[string]any)
|
||||
err = json.Unmarshal(b, &m)
|
||||
assert.NoError(suite.T(), err)
|
||||
|
||||
|
|
@ -202,11 +202,11 @@ func (suite *RepliesGetTestSuite) TestGetRepliesLast() {
|
|||
// but because we're calling the function directly, we need to set them manually.
|
||||
ctx.Params = gin.Params{
|
||||
gin.Param{
|
||||
Key: users.UsernameKey,
|
||||
Key: apiutil.UsernameKey,
|
||||
Value: targetAccount.Username,
|
||||
},
|
||||
gin.Param{
|
||||
Key: users.StatusIDKey,
|
||||
Key: apiutil.IDKey,
|
||||
Value: targetStatus.ID,
|
||||
},
|
||||
}
|
||||
|
|
@ -238,7 +238,7 @@ func (suite *RepliesGetTestSuite) TestGetRepliesLast() {
|
|||
})
|
||||
assert.Equal(suite.T(), expect, string(b))
|
||||
|
||||
m := make(map[string]interface{})
|
||||
m := make(map[string]any)
|
||||
err = json.Unmarshal(b, &m)
|
||||
assert.NoError(suite.T(), err)
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import (
|
|||
// StatusGETHandler serves the target status as an activitystreams NOTE so that other AP servers can parse it.
|
||||
func (m *Module) StatusGETHandler(c *gin.Context) {
|
||||
// usernames on our instance are always lowercase
|
||||
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||
requestedUsername := strings.ToLower(c.Param(apiutil.UsernameKey))
|
||||
if requestedUsername == "" {
|
||||
err := errors.New("no username specified in request")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
|
|
@ -38,7 +38,7 @@ func (m *Module) StatusGETHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
// status IDs on our instance are always uppercase
|
||||
requestedStatusID := strings.ToUpper(c.Param(StatusIDKey))
|
||||
requestedStatusID := strings.ToUpper(c.Param(apiutil.IDKey))
|
||||
if requestedStatusID == "" {
|
||||
err := errors.New("no status id specified in request")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import (
|
|||
|
||||
"code.superseriousbusiness.org/activity/streams"
|
||||
"code.superseriousbusiness.org/activity/streams/vocab"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/api/activitypub/users"
|
||||
apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util"
|
||||
"code.superseriousbusiness.org/gotosocial/testrig"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
|
@ -59,11 +59,11 @@ func (suite *StatusGetTestSuite) TestGetStatus() {
|
|||
// but because we're calling the function directly, we need to set them manually.
|
||||
ctx.Params = gin.Params{
|
||||
gin.Param{
|
||||
Key: users.UsernameKey,
|
||||
Key: apiutil.UsernameKey,
|
||||
Value: targetAccount.Username,
|
||||
},
|
||||
gin.Param{
|
||||
Key: users.StatusIDKey,
|
||||
Key: apiutil.IDKey,
|
||||
Value: targetStatus.ID,
|
||||
},
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ func (suite *StatusGetTestSuite) TestGetStatus() {
|
|||
suite.NoError(err)
|
||||
|
||||
// should be a Note
|
||||
m := make(map[string]interface{})
|
||||
m := make(map[string]any)
|
||||
err = json.Unmarshal(b, &m)
|
||||
suite.NoError(err)
|
||||
|
||||
|
|
@ -118,11 +118,11 @@ func (suite *StatusGetTestSuite) TestGetStatusLowercase() {
|
|||
// but because we're calling the function directly, we need to set them manually.
|
||||
ctx.Params = gin.Params{
|
||||
gin.Param{
|
||||
Key: users.UsernameKey,
|
||||
Key: apiutil.UsernameKey,
|
||||
Value: strings.ToLower(targetAccount.Username),
|
||||
},
|
||||
gin.Param{
|
||||
Key: users.StatusIDKey,
|
||||
Key: apiutil.IDKey,
|
||||
Value: strings.ToLower(targetStatus.ID),
|
||||
},
|
||||
}
|
||||
|
|
@ -139,7 +139,7 @@ func (suite *StatusGetTestSuite) TestGetStatusLowercase() {
|
|||
suite.NoError(err)
|
||||
|
||||
// should be a Note
|
||||
m := make(map[string]interface{})
|
||||
m := make(map[string]any)
|
||||
err = json.Unmarshal(b, &m)
|
||||
suite.NoError(err)
|
||||
|
||||
|
|
|
|||
|
|
@ -27,39 +27,17 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// UsernameKey is for account usernames.
|
||||
UsernameKey = "username"
|
||||
// StatusIDKey is for status IDs
|
||||
StatusIDKey = "status"
|
||||
// OnlyOtherAccountsKey is for filtering status responses.
|
||||
OnlyOtherAccountsKey = "only_other_accounts"
|
||||
// MinIDKey is for filtering status responses.
|
||||
MinIDKey = "min_id"
|
||||
// MaxIDKey is for filtering status responses.
|
||||
MaxIDKey = "max_id"
|
||||
// PageKey is for filtering status responses.
|
||||
PageKey = "page"
|
||||
|
||||
// BasePath is the base path for serving AP 'users' requests, minus the 'users' prefix.
|
||||
BasePath = "/:" + UsernameKey
|
||||
// InboxPath is for serving POST requests to a user's inbox with the given username key.
|
||||
InboxPath = BasePath + "/" + uris.InboxPath
|
||||
// OutboxPath is for serving GET requests to a user's outbox with the given username key.
|
||||
OutboxPath = BasePath + "/" + uris.OutboxPath
|
||||
// FollowersPath is for serving GET request's to a user's followers list, with the given username key.
|
||||
FollowersPath = BasePath + "/" + uris.FollowersPath
|
||||
// FollowingPath is for serving GET request's to a user's following list, with the given username key.
|
||||
FollowingPath = BasePath + "/" + uris.FollowingPath
|
||||
// FeaturedCollectionPath is for serving GET requests to a user's list of featured (pinned) statuses.
|
||||
OnlyOtherAccountsKey = "only_other_accounts"
|
||||
BasePath = "/:" + apiutil.UsernameKey
|
||||
InboxPath = BasePath + "/" + uris.InboxPath
|
||||
OutboxPath = BasePath + "/" + uris.OutboxPath
|
||||
FollowersPath = BasePath + "/" + uris.FollowersPath
|
||||
FollowingPath = BasePath + "/" + uris.FollowingPath
|
||||
FeaturedCollectionPath = BasePath + "/" + uris.CollectionsPath + "/" + uris.FeaturedPath
|
||||
// StatusPath is for serving GET requests to a particular status by a user, with the given username key and status ID
|
||||
StatusPath = BasePath + "/" + uris.StatusesPath + "/:" + StatusIDKey
|
||||
// StatusRepliesPath is for serving the replies collection of a status.
|
||||
StatusRepliesPath = StatusPath + "/replies"
|
||||
// AcceptPath is for serving accepts of a status.
|
||||
AcceptPath = BasePath + "/" + uris.AcceptsPath + "/:" + apiutil.IDKey
|
||||
// AuthorizationsPath is for serving authorizations of an interaction.
|
||||
AuthorizationsPath = BasePath + "/" + uris.AuthorizationsPath + "/:" + apiutil.IDKey
|
||||
StatusPath = BasePath + "/" + uris.StatusesPath + "/:" + apiutil.IDKey
|
||||
StatusRepliesPath = StatusPath + "/replies"
|
||||
AcceptPath = BasePath + "/" + uris.AcceptsPath + "/:" + apiutil.IDKey
|
||||
AuthorizationsPath = BasePath + "/" + uris.AuthorizationsPath + "/:" + apiutil.IDKey
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import (
|
|||
// request is blocked.
|
||||
func (m *Module) UsersGETHandler(c *gin.Context) {
|
||||
// usernames on our instance are always lowercase
|
||||
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||
requestedUsername := strings.ToLower(c.Param(apiutil.UsernameKey))
|
||||
if requestedUsername == "" {
|
||||
err := errors.New("no username specified in request")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
|
|
@ -51,13 +51,17 @@ func (m *Module) UsersGETHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// If HTML is requested, redirect
|
||||
// to user's profile instead.
|
||||
if contentType == string(apiutil.TextHTML) {
|
||||
// redirect to the user's profile
|
||||
c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)
|
||||
return
|
||||
}
|
||||
|
||||
resp, errWithCode := m.processor.Fedi().UserGet(c.Request.Context(), requestedUsername, c.Request.URL)
|
||||
resp, errWithCode := m.processor.Fedi().UserGet(
|
||||
c.Request.Context(),
|
||||
requestedUsername,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"code.superseriousbusiness.org/activity/streams"
|
||||
"code.superseriousbusiness.org/activity/streams/vocab"
|
||||
"code.superseriousbusiness.org/gotosocial/internal/api/activitypub/users"
|
||||
apiutil "code.superseriousbusiness.org/gotosocial/internal/api/util"
|
||||
"code.superseriousbusiness.org/gotosocial/testrig"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
|
@ -57,7 +58,7 @@ func (suite *UserGetTestSuite) TestGetUser() {
|
|||
// but because we're calling the function directly, we need to set them manually.
|
||||
ctx.Params = gin.Params{
|
||||
gin.Param{
|
||||
Key: users.UsernameKey,
|
||||
Key: apiutil.UsernameKey,
|
||||
Value: targetAccount.Username,
|
||||
},
|
||||
}
|
||||
|
|
@ -74,7 +75,7 @@ func (suite *UserGetTestSuite) TestGetUser() {
|
|||
suite.NoError(err)
|
||||
|
||||
// should be a Person
|
||||
m := make(map[string]interface{})
|
||||
m := make(map[string]any)
|
||||
err = json.Unmarshal(b, &m)
|
||||
suite.NoError(err)
|
||||
|
||||
|
|
@ -125,7 +126,7 @@ func (suite *UserGetTestSuite) TestGetUserPublicKeyDeleted() {
|
|||
// but because we're calling the function directly, we need to set them manually.
|
||||
ctx.Params = gin.Params{
|
||||
gin.Param{
|
||||
Key: users.UsernameKey,
|
||||
Key: apiutil.UsernameKey,
|
||||
Value: targetAccount.Username,
|
||||
},
|
||||
}
|
||||
|
|
@ -142,7 +143,7 @@ func (suite *UserGetTestSuite) TestGetUserPublicKeyDeleted() {
|
|||
suite.NoError(err)
|
||||
|
||||
// should be a Person
|
||||
m := make(map[string]interface{})
|
||||
m := make(map[string]any)
|
||||
err = json.Unmarshal(b, &m)
|
||||
suite.NoError(err)
|
||||
|
||||
|
|
|
|||
|
|
@ -206,17 +206,54 @@ func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
|
|||
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)
|
||||
}
|
||||
// Check if a status code was returned
|
||||
// from the failed dereference attempt.
|
||||
switch statusCode := gtserror.StatusCode(err); statusCode {
|
||||
|
||||
err := gtserror.Newf("error dereferencing account %s: %w", pubKeyAuth.OwnerURI, err)
|
||||
return nil, gtserror.NewErrorInternalError(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.
|
||||
err := gtserror.Newf(
|
||||
"received 403 Forbidden fetching account %s, cannot process request: %w",
|
||||
pubKeyAuth.OwnerURI, err,
|
||||
)
|
||||
return nil, gtserror.NewErrorUnauthorized(err)
|
||||
|
||||
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.
|
||||
err := gtserror.Newf(
|
||||
"received 401 Unauthorized fetching account %s, cannot process request: %w",
|
||||
pubKeyAuth.OwnerURI, err,
|
||||
)
|
||||
return nil, gtserror.NewErrorUnauthorized(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.
|
||||
err := gtserror.Newf(
|
||||
"requesting account %s is gone, cannot process request: %w",
|
||||
pubKeyAuth.OwnerURI, err,
|
||||
)
|
||||
return nil, gtserror.NewErrorGone(err)
|
||||
|
||||
default:
|
||||
// In all other cases, return 401 Unauthorized,
|
||||
// as we could not continue with this request.
|
||||
err := gtserror.Newf(
|
||||
"could not dereference requesting account %s: %w",
|
||||
pubKeyAuth.OwnerURI, err,
|
||||
)
|
||||
return nil, gtserror.NewErrorUnauthorized(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Catch a possible (but very rare) race condition where
|
||||
|
|
|
|||
|
|
@ -217,17 +217,21 @@ func (f *Federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr
|
|||
pubKeyAuth, errWithCode := f.AuthenticateFederatedRequest(ctx, receivingAccount.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.
|
||||
|
||||
// If codes 400, 401, or 403, obey the go-fed
|
||||
// interface by writing the header and bailing.
|
||||
case http.StatusUnauthorized,
|
||||
http.StatusForbidden,
|
||||
http.StatusBadRequest:
|
||||
w.WriteHeader(errWithCode.Code())
|
||||
|
||||
// If the requesting account's key 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.
|
||||
case http.StatusGone:
|
||||
// If the requesting account's key 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.
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,24 +30,11 @@ 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.
|
||||
}
|
||||
|
||||
func (p *Processor) authenticate(ctx context.Context, requestedUser string) (*commonAuth, gtserror.WithCode) {
|
||||
// First get the requested (receiving) LOCAL account with 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)
|
||||
}
|
||||
|
||||
// Account just not found in the db.
|
||||
return nil, gtserror.NewErrorNotFound(err)
|
||||
}
|
||||
|
||||
// Ensure request signed, and use signature URI to
|
||||
// get requesting account, dereferencing if necessary.
|
||||
pubKeyAuth, errWithCode := p.federator.AuthenticateFederatedRequest(ctx, requestedUser)
|
||||
|
|
@ -55,31 +42,46 @@ func (p *Processor) authenticate(ctx context.Context, requestedUser string) (*co
|
|||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// Get the requested local account
|
||||
// with given username from database.
|
||||
receiver, err := p.state.DB.GetAccountByUsernameDomain(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 receiver == nil {
|
||||
err := gtserror.Newf("account %s not found in the db", requestedUser)
|
||||
return nil, gtserror.NewErrorNotFound(err)
|
||||
}
|
||||
|
||||
if pubKeyAuth.Handshaking {
|
||||
// We're still handshaking so we
|
||||
// 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)
|
||||
// Ensure receiver does not block requester.
|
||||
blocked, err := p.state.DB.IsBlocked(ctx, receiver.ID, requester.ID)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error checking block: %w", err)
|
||||
err := gtserror.Newf("db error checking block: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
} else if blocked {
|
||||
const text = "block exists between accounts"
|
||||
}
|
||||
|
||||
if blocked {
|
||||
var text = requestedUser + " blocks " + requester.Username
|
||||
return nil, gtserror.NewErrorForbidden(errors.New(text))
|
||||
}
|
||||
|
||||
return &commonAuth{
|
||||
requestingAcct: requester,
|
||||
receivingAcct: receiver,
|
||||
requester: requester,
|
||||
receiver: receiver,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +122,7 @@ func (p *Processor) validateIntReqRequest(
|
|||
|
||||
// Ensure interaction request was accepted
|
||||
// by the account in the request path.
|
||||
if req.TargetAccountID != auth.receivingAcct.ID {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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 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 emoji == nil {
|
||||
err := gtserror.Newf("emoji %s not found in the db", emojiID)
|
||||
return nil, gtserror.NewErrorNotFound(err)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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.NewErrorNotFound(fmt.Errorf("database error getting emoji with id %s: %s", requestedEmojiID, 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 *requestedEmoji.Disabled {
|
||||
return nil, gtserror.NewErrorNotFound(fmt.Errorf("emoji with id %s has been disabled", requestedEmojiID))
|
||||
}
|
||||
|
||||
apEmoji, err := p.converter.EmojiToAS(ctx, requestedEmoji)
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
// Real db error.
|
||||
err := fmt.Errorf("db error getting account with username %s: %w", requestedUsername, 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
// 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,
|
||||
// if we're still handshaking then don't be coy: just serve
|
||||
// the AP representation of the requested 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 // likely 401
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
||||
// Auth passed, generate the proper AP representation.
|
||||
accountable, err := p.converter.AccountToAS(ctx, receiver)
|
||||
// Generate the proper AP representation.
|
||||
accountable, err := p.converter.AccountToAS(ctx, auth.receiver)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error converting account: %w", err)
|
||||
err := gtserror.Newf("error converting to accountable: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
data, err := ap.Serialize(accountable)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error serializing accountable: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// 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 acct == nil {
|
||||
err := gtserror.Newf("account %s not found in the db", requestedUser)
|
||||
return nil, gtserror.NewErrorNotFound(err)
|
||||
}
|
||||
|
||||
// Generate minimal AP representation.
|
||||
accountable, err := p.converter.AccountToASMinimal(ctx, acct)
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error converting to accountable: %w", 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
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))
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ func (m *Module) returnAPAccount(
|
|||
targetUsername string,
|
||||
contentType string,
|
||||
) {
|
||||
user, errWithCode := m.processor.Fedi().UserGet(c.Request.Context(), targetUsername, c.Request.URL)
|
||||
user, errWithCode := m.processor.Fedi().UserGet(c.Request.Context(), targetUsername)
|
||||
if errWithCode != nil {
|
||||
apiutil.WebErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue