mirror of
				https://github.com/superseriousbusiness/gotosocial
				synced 2025-06-05 21:59:39 +02:00 
			
		
		
		
	[feature] More consistent API error handling (#637)
* update templates * start reworking api error handling * update template * return AP status at web endpoint if negotiated * start making api error handling much more consistent * update account endpoints to new error handling * use new api error handling in admin endpoints * go fmt ./... * use api error logic in app * use generic error handling in auth * don't export generic error handler * don't defer clearing session * user nicer error handling on oidc callback handler * tidy up the sign in handler * tidy up the token handler * use nicer error handling in blocksget * auth emojis endpoint * fix up remaining api endpoints * fix whoopsie during login flow * regenerate swagger docs * change http error logging to debug
This commit is contained in:
		@@ -3,9 +3,9 @@ package instance
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/api"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/config"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/gtserror"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
)
 | 
			
		||||
@@ -30,22 +30,19 @@ import (
 | 
			
		||||
//     description: "Instance information."
 | 
			
		||||
//     schema:
 | 
			
		||||
//       "$ref": "#/definitions/instance"
 | 
			
		||||
//   '406':
 | 
			
		||||
//      description: not acceptable
 | 
			
		||||
//   '500':
 | 
			
		||||
//      description: internal error
 | 
			
		||||
func (m *Module) InstanceInformationGETHandler(c *gin.Context) {
 | 
			
		||||
	l := logrus.WithField("func", "InstanceInformationGETHandler")
 | 
			
		||||
 | 
			
		||||
	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
 | 
			
		||||
		c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
 | 
			
		||||
		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	host := config.GetHost()
 | 
			
		||||
 | 
			
		||||
	instance, err := m.processor.InstanceGet(c.Request.Context(), host)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		l.Debugf("error getting instance from processor: %s", err)
 | 
			
		||||
		c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
 | 
			
		||||
	instance, errWithCode := m.processor.InstanceGet(c.Request.Context(), config.GetHost())
 | 
			
		||||
	if errWithCode != nil {
 | 
			
		||||
		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,13 @@
 | 
			
		||||
package instance
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/api"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/api/model"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/gtserror"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/oauth"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -82,52 +82,51 @@ import (
 | 
			
		||||
//     description: "The newly updated instance."
 | 
			
		||||
//     schema:
 | 
			
		||||
//       "$ref": "#/definitions/instance"
 | 
			
		||||
//   '401':
 | 
			
		||||
//      description: unauthorized
 | 
			
		||||
//   '400':
 | 
			
		||||
//      description: bad request
 | 
			
		||||
//   '401':
 | 
			
		||||
//      description: unauthorized
 | 
			
		||||
//   '403':
 | 
			
		||||
//      description: forbidden
 | 
			
		||||
//   '404':
 | 
			
		||||
//      description: not found
 | 
			
		||||
//   '406':
 | 
			
		||||
//      description: not acceptable
 | 
			
		||||
//   '500':
 | 
			
		||||
//      description: internal server error
 | 
			
		||||
func (m *Module) InstanceUpdatePATCHHandler(c *gin.Context) {
 | 
			
		||||
	l := logrus.WithField("func", "InstanceUpdatePATCHHandler")
 | 
			
		||||
	authed, err := oauth.Authed(c, true, true, true, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		l.Debugf("couldn't auth: %s", err)
 | 
			
		||||
		c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
 | 
			
		||||
		api.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGet)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := api.NegotiateAccept(c, api.JSONAcceptHeaders...); err != nil {
 | 
			
		||||
		c.JSON(http.StatusNotAcceptable, gin.H{"error": err.Error()})
 | 
			
		||||
		api.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGet)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// only admins can update instance settings
 | 
			
		||||
	if !authed.User.Admin {
 | 
			
		||||
		l.Debug("user is not an admin so cannot update instance settings")
 | 
			
		||||
		c.JSON(http.StatusUnauthorized, gin.H{"error": "not an admin"})
 | 
			
		||||
		err := errors.New("user is not an admin so cannot update instance settings")
 | 
			
		||||
		api.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGet)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	l.Debug("parsing request form")
 | 
			
		||||
	form := &model.InstanceSettingsUpdateRequest{}
 | 
			
		||||
	if err := c.ShouldBind(&form); err != nil || form == nil {
 | 
			
		||||
		l.Debugf("could not parse form from request: %s", err)
 | 
			
		||||
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
 | 
			
		||||
	if err := c.ShouldBind(&form); err != nil {
 | 
			
		||||
		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	l.Debugf("parsed form: %+v", form)
 | 
			
		||||
 | 
			
		||||
	// if everything on the form is nil, then nothing has been set and we shouldn't continue
 | 
			
		||||
	if form.Title == nil && form.ContactUsername == nil && form.ContactEmail == nil && form.ShortDescription == nil && form.Description == nil && form.Terms == nil && form.Avatar == nil && form.Header == nil {
 | 
			
		||||
		l.Debugf("could not parse form from request")
 | 
			
		||||
		c.JSON(http.StatusBadRequest, gin.H{"error": "empty form submitted"})
 | 
			
		||||
		err := errors.New("empty form submitted")
 | 
			
		||||
		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	i, errWithCode := m.processor.InstancePatch(c.Request.Context(), form)
 | 
			
		||||
	if errWithCode != nil {
 | 
			
		||||
		l.Debugf("error with instance patch request: %s", errWithCode.Error())
 | 
			
		||||
		c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
 | 
			
		||||
		api.ErrorHandler(c, errWithCode, m.processor.InstanceGet)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/suite"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/api/client/instance"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/internal/oauth"
 | 
			
		||||
	"github.com/superseriousbusiness/gotosocial/testrig"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -125,6 +126,67 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {
 | 
			
		||||
	suite.Equal(`{"uri":"http://localhost:8080","title":"localhost:8080","description":"","short_description":"\u003cp\u003eThis is some html, which is \u003cem\u003eallowed\u003c/em\u003e in short descriptions.\u003c/p\u003e","email":"","version":"","registrations":true,"approval_required":true,"invites_enabled":false,"urls":{"streaming_api":"wss://localhost:8080"},"stats":{"domain_count":0,"status_count":16,"user_count":4},"thumbnail":"","max_toot_chars":5000}`, string(b))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *InstancePatchTestSuite) TestInstancePatch4() {
 | 
			
		||||
	requestBody, w, err := testrig.CreateMultipartFormData(
 | 
			
		||||
		"", "",
 | 
			
		||||
		map[string]string{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	bodyBytes := requestBody.Bytes()
 | 
			
		||||
 | 
			
		||||
	// set up the request
 | 
			
		||||
	recorder := httptest.NewRecorder()
 | 
			
		||||
	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, instance.InstanceInformationPath, w.FormDataContentType())
 | 
			
		||||
 | 
			
		||||
	// call the handler
 | 
			
		||||
	suite.instanceModule.InstanceUpdatePATCHHandler(ctx)
 | 
			
		||||
 | 
			
		||||
	suite.Equal(http.StatusBadRequest, recorder.Code)
 | 
			
		||||
 | 
			
		||||
	result := recorder.Result()
 | 
			
		||||
	defer result.Body.Close()
 | 
			
		||||
 | 
			
		||||
	b, err := io.ReadAll(result.Body)
 | 
			
		||||
	suite.NoError(err)
 | 
			
		||||
 | 
			
		||||
	suite.Equal(`{"error":"Bad Request: empty form submitted"}`, string(b))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *InstancePatchTestSuite) TestInstancePatch5() {
 | 
			
		||||
	requestBody, w, err := testrig.CreateMultipartFormData(
 | 
			
		||||
		"", "",
 | 
			
		||||
		map[string]string{
 | 
			
		||||
			"short_description": "<p>This is some html, which is <em>allowed</em> in short descriptions.</p>",
 | 
			
		||||
		})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	bodyBytes := requestBody.Bytes()
 | 
			
		||||
 | 
			
		||||
	// set up the request
 | 
			
		||||
	recorder := httptest.NewRecorder()
 | 
			
		||||
	ctx := suite.newContext(recorder, http.MethodPatch, bodyBytes, instance.InstanceInformationPath, w.FormDataContentType())
 | 
			
		||||
 | 
			
		||||
	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
 | 
			
		||||
	ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
 | 
			
		||||
	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
 | 
			
		||||
	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
 | 
			
		||||
 | 
			
		||||
	// call the handler
 | 
			
		||||
	suite.instanceModule.InstanceUpdatePATCHHandler(ctx)
 | 
			
		||||
 | 
			
		||||
	suite.Equal(http.StatusForbidden, recorder.Code)
 | 
			
		||||
 | 
			
		||||
	result := recorder.Result()
 | 
			
		||||
	defer result.Body.Close()
 | 
			
		||||
 | 
			
		||||
	b, err := io.ReadAll(result.Body)
 | 
			
		||||
	suite.NoError(err)
 | 
			
		||||
 | 
			
		||||
	suite.Equal(`{"error":"Forbidden: user is not an admin so cannot update instance settings"}`, string(b))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestInstancePatchTestSuite(t *testing.T) {
 | 
			
		||||
	suite.Run(t, &InstancePatchTestSuite{})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user