diff options
Diffstat (limited to 'internal/api/client')
-rw-r--r-- | internal/api/client/auth/auth_test.go | 3 | ||||
-rw-r--r-- | internal/api/client/auth/middleware.go | 3 | ||||
-rw-r--r-- | internal/api/client/auth/token.go | 30 | ||||
-rw-r--r-- | internal/api/client/followrequest/accept.go | 57 | ||||
-rw-r--r-- | internal/api/client/followrequest/deny.go | 27 | ||||
-rw-r--r-- | internal/api/client/followrequest/followrequest.go | 68 | ||||
-rw-r--r-- | internal/api/client/followrequest/get.go | 51 | ||||
-rw-r--r-- | internal/api/client/instance/instance.go | 2 | ||||
-rw-r--r-- | internal/api/client/instance/instanceget.go | 1 | ||||
-rw-r--r-- | internal/api/client/media/media.go | 2 | ||||
-rw-r--r-- | internal/api/client/media/mediacreate.go | 20 | ||||
-rw-r--r-- | internal/api/client/media/mediaget.go | 2 |
12 files changed, 253 insertions, 13 deletions
diff --git a/internal/api/client/auth/auth_test.go b/internal/api/client/auth/auth_test.go index 7ec788a0e..48d2a2508 100644 --- a/internal/api/client/auth/auth_test.go +++ b/internal/api/client/auth/auth_test.go @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/db/pg" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/oauth" "golang.org/x/crypto/bcrypt" @@ -103,7 +104,7 @@ func (suite *AuthTestSuite) SetupTest() { log := logrus.New() log.SetLevel(logrus.TraceLevel) - db, err := db.NewPostgresService(context.Background(), suite.config, log) + db, err := pg.NewPostgresService(context.Background(), suite.config, log) if err != nil { logrus.Panicf("error creating database connection: %s", err) } diff --git a/internal/api/client/auth/middleware.go b/internal/api/client/auth/middleware.go index 8313b7c28..2a63cbdb6 100644 --- a/internal/api/client/auth/middleware.go +++ b/internal/api/client/auth/middleware.go @@ -33,7 +33,7 @@ func (m *Module) OauthTokenMiddleware(c *gin.Context) { l := m.log.WithField("func", "OauthTokenMiddleware") l.Trace("entering OauthTokenMiddleware") - ti, err := m.server.ValidationBearerToken(c.Request) + ti, err := m.server.ValidationBearerToken(c.Copy().Request) if err != nil { l.Tracef("could not validate token: %s", err) return @@ -74,4 +74,5 @@ func (m *Module) OauthTokenMiddleware(c *gin.Context) { c.Set(oauth.SessionAuthorizedApplication, app) l.Tracef("set gin context %s to %+v", oauth.SessionAuthorizedApplication, app) } + c.Next() } diff --git a/internal/api/client/auth/token.go b/internal/api/client/auth/token.go index c531a3009..798a88d19 100644 --- a/internal/api/client/auth/token.go +++ b/internal/api/client/auth/token.go @@ -20,16 +20,46 @@ package auth import ( "net/http" + "net/url" "github.com/gin-gonic/gin" ) +type tokenBody struct { + ClientID *string `form:"client_id" json:"client_id" xml:"client_id"` + ClientSecret *string `form:"client_secret" json:"client_secret" xml:"client_secret"` + Code *string `form:"code" json:"code" xml:"code"` + GrantType *string `form:"grant_type" json:"grant_type" xml:"grant_type"` + RedirectURI *string `form:"redirect_uri" json:"redirect_uri" xml:"redirect_uri"` +} + // TokenPOSTHandler should be served as a POST at https://example.org/oauth/token // The idea here is to serve an oauth access token to a user, which can be used for authorizing against non-public APIs. // See https://docs.joinmastodon.org/methods/apps/oauth/#obtain-a-token func (m *Module) TokenPOSTHandler(c *gin.Context) { l := m.log.WithField("func", "TokenPOSTHandler") l.Trace("entered TokenPOSTHandler") + + form := &tokenBody{} + if err := c.ShouldBind(form); err == nil { + c.Request.Form = url.Values{} + if form.ClientID != nil { + c.Request.Form.Set("client_id", *form.ClientID) + } + if form.ClientSecret != nil { + c.Request.Form.Set("client_secret", *form.ClientSecret) + } + if form.Code != nil { + c.Request.Form.Set("code", *form.Code) + } + if form.GrantType != nil { + c.Request.Form.Set("grant_type", *form.GrantType) + } + if form.RedirectURI != nil { + c.Request.Form.Set("redirect_uri", *form.RedirectURI) + } + } + if err := m.server.HandleTokenRequest(c.Writer, c.Request); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } diff --git a/internal/api/client/followrequest/accept.go b/internal/api/client/followrequest/accept.go new file mode 100644 index 000000000..45dc1a2af --- /dev/null +++ b/internal/api/client/followrequest/accept.go @@ -0,0 +1,57 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + 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 followrequest + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/superseriousbusiness/gotosocial/internal/oauth" +) + +// FollowRequestAcceptPOSTHandler deals with follow request accepting. It should be served at +// /api/v1/follow_requests/:id/authorize +func (m *Module) FollowRequestAcceptPOSTHandler(c *gin.Context) { + l := m.log.WithField("func", "statusCreatePOSTHandler") + authed, err := oauth.Authed(c, true, true, true, true) + if err != nil { + l.Debugf("couldn't auth: %s", err) + c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) + return + } + + if authed.User.Disabled || !authed.User.Approved || !authed.Account.SuspendedAt.IsZero() { + l.Debugf("couldn't auth: %s", err) + c.JSON(http.StatusForbidden, gin.H{"error": "account is disabled, not yet approved, or suspended"}) + return + } + + originAccountID := c.Param(IDKey) + if originAccountID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "no follow request origin account id provided"}) + return + } + + if errWithCode := m.processor.FollowRequestAccept(authed, originAccountID); errWithCode != nil { + l.Debug(errWithCode.Error()) + c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) + return + } + c.Status(http.StatusOK) +} diff --git a/internal/api/client/followrequest/deny.go b/internal/api/client/followrequest/deny.go new file mode 100644 index 000000000..c1a9e4dbf --- /dev/null +++ b/internal/api/client/followrequest/deny.go @@ -0,0 +1,27 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + 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 followrequest + +import "github.com/gin-gonic/gin" + +// FollowRequestDenyPOSTHandler deals with follow request rejection. It should be served at +// /api/v1/follow_requests/:id/reject +func (m *Module) FollowRequestDenyPOSTHandler(c *gin.Context) { + +} diff --git a/internal/api/client/followrequest/followrequest.go b/internal/api/client/followrequest/followrequest.go new file mode 100644 index 000000000..8be957009 --- /dev/null +++ b/internal/api/client/followrequest/followrequest.go @@ -0,0 +1,68 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + 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 followrequest + +import ( + "net/http" + + "github.com/sirupsen/logrus" + "github.com/superseriousbusiness/gotosocial/internal/api" + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/message" + "github.com/superseriousbusiness/gotosocial/internal/router" +) + +const ( + // IDKey is for status UUIDs + IDKey = "id" + // BasePath is the base path for serving the follow request API + BasePath = "/api/v1/follow_requests" + // BasePathWithID is just the base path with the ID key in it. + // Use this anywhere you need to know the ID of the follow request being queried. + BasePathWithID = BasePath + "/:" + IDKey + + // AcceptPath is used for accepting follow requests + AcceptPath = BasePathWithID + "/authorize" + // DenyPath is used for denying follow requests + DenyPath = BasePathWithID + "/reject" +) + +// Module implements the ClientAPIModule interface for every related to interacting with follow requests +type Module struct { + config *config.Config + processor message.Processor + log *logrus.Logger +} + +// New returns a new follow request module +func New(config *config.Config, processor message.Processor, log *logrus.Logger) api.ClientModule { + return &Module{ + config: config, + processor: processor, + log: log, + } +} + +// Route attaches all routes from this module to the given router +func (m *Module) Route(r router.Router) error { + r.AttachHandler(http.MethodGet, BasePath, m.FollowRequestGETHandler) + r.AttachHandler(http.MethodPost, AcceptPath, m.FollowRequestAcceptPOSTHandler) + r.AttachHandler(http.MethodPost, DenyPath, m.FollowRequestDenyPOSTHandler) + return nil +} diff --git a/internal/api/client/followrequest/get.go b/internal/api/client/followrequest/get.go new file mode 100644 index 000000000..3f02ee02a --- /dev/null +++ b/internal/api/client/followrequest/get.go @@ -0,0 +1,51 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + 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 followrequest + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/superseriousbusiness/gotosocial/internal/oauth" +) + +// FollowRequestGETHandler allows clients to get a list of their incoming follow requests. +func (m *Module) FollowRequestGETHandler(c *gin.Context) { + l := m.log.WithField("func", "statusCreatePOSTHandler") + authed, err := oauth.Authed(c, true, true, true, true) + if err != nil { + l.Debugf("couldn't auth: %s", err) + c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) + return + } + + if authed.User.Disabled || !authed.User.Approved || !authed.Account.SuspendedAt.IsZero() { + l.Debugf("couldn't auth: %s", err) + c.JSON(http.StatusForbidden, gin.H{"error": "account is disabled, not yet approved, or suspended"}) + return + } + + accts, errWithCode := m.processor.FollowRequestsGet(authed) + if errWithCode != nil { + c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) + return + } + + c.JSON(http.StatusOK, accts) +} diff --git a/internal/api/client/instance/instance.go b/internal/api/client/instance/instance.go index ed7c18718..ba54480a5 100644 --- a/internal/api/client/instance/instance.go +++ b/internal/api/client/instance/instance.go @@ -11,7 +11,7 @@ import ( ) const ( - // InstanceInformationPath + // InstanceInformationPath is for serving instance info requests InstanceInformationPath = "api/v1/instance" ) diff --git a/internal/api/client/instance/instanceget.go b/internal/api/client/instance/instanceget.go index f8e82c096..6ae419a83 100644 --- a/internal/api/client/instance/instanceget.go +++ b/internal/api/client/instance/instanceget.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" ) +// InstanceInformationGETHandler is for serving instance information at /api/v1/instance func (m *Module) InstanceInformationGETHandler(c *gin.Context) { l := m.log.WithField("func", "InstanceInformationGETHandler") diff --git a/internal/api/client/media/media.go b/internal/api/client/media/media.go index e45eec7ea..f68a73c2c 100644 --- a/internal/api/client/media/media.go +++ b/internal/api/client/media/media.go @@ -33,8 +33,10 @@ import ( // BasePath is the base API path for making media requests const BasePath = "/api/v1/media" + // IDKey is the key for media attachment IDs const IDKey = "id" + // BasePathWithID corresponds to a media attachment with the given ID const BasePathWithID = BasePath + "/:" + IDKey diff --git a/internal/api/client/media/mediacreate.go b/internal/api/client/media/mediacreate.go index c0b4a80d7..9f4702b6b 100644 --- a/internal/api/client/media/mediacreate.go +++ b/internal/api/client/media/mediacreate.go @@ -35,30 +35,32 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) { authed, err := oauth.Authed(c, true, true, true, true) // posting new media is serious business so we want *everything* if err != nil { l.Debugf("couldn't auth: %s", err) - c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) + c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) return } // extract the media create form from the request context l.Tracef("parsing request form: %s", c.Request.Form) - var form model.AttachmentRequest + form := &model.AttachmentRequest{} if err := c.ShouldBind(&form); err != nil { - l.Debugf("could not parse form from request: %s", err) - c.JSON(http.StatusBadRequest, gin.H{"error": "missing one or more required form values"}) + l.Debugf("error parsing form: %s", err) + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("could not parse form: %s", err)}) return } // Give the fields on the request form a first pass to make sure the request is superficially valid. l.Tracef("validating form %+v", form) - if err := validateCreateMedia(&form, m.config.MediaConfig); err != nil { + if err := validateCreateMedia(form, m.config.MediaConfig); err != nil { l.Debugf("error validating form: %s", err) - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()}) return } - mastoAttachment, err := m.processor.MediaCreate(authed, &form) + l.Debug("calling processor media create func") + mastoAttachment, err := m.processor.MediaCreate(authed, form) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + l.Debugf("error creating attachment: %s", err) + c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()}) return } @@ -67,7 +69,7 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) { func validateCreateMedia(form *model.AttachmentRequest, config *config.MediaConfig) error { // check there actually is a file attached and it's not size 0 - if form.File == nil || form.File.Size == 0 { + if form.File == nil { return errors.New("no attachment given") } diff --git a/internal/api/client/media/mediaget.go b/internal/api/client/media/mediaget.go index 31c40a5aa..7acb475ce 100644 --- a/internal/api/client/media/mediaget.go +++ b/internal/api/client/media/mediaget.go @@ -43,7 +43,7 @@ func (m *Module) MediaGETHandler(c *gin.Context) { attachment, errWithCode := m.processor.MediaGet(authed, attachmentID) if errWithCode != nil { - c.JSON(errWithCode.Code(),gin.H{"error": errWithCode.Safe()}) + c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) return } |