From 694a49058951de31cca4ea061e2c08d44e712612 Mon Sep 17 00:00:00 2001 From: tobi <31960611+tsmethurst@users.noreply.github.com> Date: Sat, 11 Jun 2022 10:39:39 +0200 Subject: [PATCH] [feature] Add `created_at` and `error_description` to `/oauth/token` endpoint (#645) * start fiddling about with oauth server * start returning more helpful errors from oauth * test helpful(ish) token errors * add missing license header --- cmd/gotosocial/action/server/server.go | 2 +- cmd/gotosocial/action/testrig/testrig.go | 2 +- internal/api/client/auth/auth.go | 5 +- internal/api/client/auth/auth_test.go | 12 +- internal/api/client/auth/authorize.go | 2 +- internal/api/client/auth/authorize_test.go | 2 +- internal/api/client/auth/token.go | 69 +++++-- internal/api/client/auth/token_test.go | 215 +++++++++++++++++++++ internal/api/errorhandling.go | 27 +++ internal/oauth/errors.go | 26 +++ internal/oauth/server.go | 24 ++- internal/processing/oauth.go | 35 ++++ internal/processing/processor.go | 3 + testrig/testmodels.go | 17 ++ 14 files changed, 411 insertions(+), 30 deletions(-) create mode 100644 internal/api/client/auth/token_test.go create mode 100644 internal/oauth/errors.go create mode 100644 internal/processing/oauth.go diff --git a/cmd/gotosocial/action/server/server.go b/cmd/gotosocial/action/server/server.go index dd41f7708..abb373426 100644 --- a/cmd/gotosocial/action/server/server.go +++ b/cmd/gotosocial/action/server/server.go @@ -165,7 +165,7 @@ } // build client api modules - authModule := auth.New(dbService, oauthServer, idp, processor) + authModule := auth.New(dbService, idp, processor) accountModule := account.New(processor) instanceModule := instance.New(processor) appsModule := app.New(processor) diff --git a/cmd/gotosocial/action/testrig/testrig.go b/cmd/gotosocial/action/testrig/testrig.go index cb0557771..8fc87de34 100644 --- a/cmd/gotosocial/action/testrig/testrig.go +++ b/cmd/gotosocial/action/testrig/testrig.go @@ -108,7 +108,7 @@ } // build client api modules - authModule := auth.New(dbService, oauthServer, idp, processor) + authModule := auth.New(dbService, idp, processor) accountModule := account.New(processor) instanceModule := instance.New(processor) appsModule := app.New(processor) diff --git a/internal/api/client/auth/auth.go b/internal/api/client/auth/auth.go index 10d374838..a097f8004 100644 --- a/internal/api/client/auth/auth.go +++ b/internal/api/client/auth/auth.go @@ -23,7 +23,6 @@ "github.com/superseriousbusiness/gotosocial/internal/api" "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oidc" "github.com/superseriousbusiness/gotosocial/internal/processing" "github.com/superseriousbusiness/gotosocial/internal/router" @@ -68,16 +67,14 @@ // Module implements the ClientAPIModule interface for type Module struct { db db.DB - server oauth.Server idp oidc.IDP processor processing.Processor } // New returns a new auth module -func New(db db.DB, server oauth.Server, idp oidc.IDP, processor processing.Processor) api.ClientModule { +func New(db db.DB, idp oidc.IDP, processor processing.Processor) api.ClientModule { return &Module{ db: db, - server: server, idp: idp, processor: processor, } diff --git a/internal/api/client/auth/auth_test.go b/internal/api/client/auth/auth_test.go index f222f714f..6a7c6ab99 100644 --- a/internal/api/client/auth/auth_test.go +++ b/internal/api/client/auth/auth_test.go @@ -19,6 +19,7 @@ package auth_test import ( + "bytes" "context" "fmt" "net/http/httptest" @@ -99,7 +100,7 @@ func (suite *AuthStandardTestSuite) SetupTest() { if err != nil { panic(err) } - suite.authModule = auth.New(suite.db, suite.oauthServer, suite.idp, suite.processor).(*auth.Module) + suite.authModule = auth.New(suite.db, suite.idp, suite.processor).(*auth.Module) testrig.StandardDBSetup(suite.db, suite.testAccounts) } @@ -107,7 +108,7 @@ func (suite *AuthStandardTestSuite) TearDownTest() { testrig.StandardDBTeardown(suite.db) } -func (suite *AuthStandardTestSuite) newContext(requestMethod string, requestPath string) (*gin.Context, *httptest.ResponseRecorder) { +func (suite *AuthStandardTestSuite) newContext(requestMethod string, requestPath string, requestBody []byte, bodyContentType string) (*gin.Context, *httptest.ResponseRecorder) { // create the recorder and gin test context recorder := httptest.NewRecorder() ctx, engine := gin.CreateTestContext(recorder) @@ -120,9 +121,14 @@ func (suite *AuthStandardTestSuite) newContext(requestMethod string, requestPath host := config.GetHost() baseURI := fmt.Sprintf("%s://%s", protocol, host) requestURI := fmt.Sprintf("%s/%s", baseURI, requestPath) - ctx.Request = httptest.NewRequest(requestMethod, requestURI, nil) // the endpoint we're hitting + + ctx.Request = httptest.NewRequest(requestMethod, requestURI, bytes.NewReader(requestBody)) // the endpoint we're hitting ctx.Request.Header.Set("accept", "text/html") + if bodyContentType != "" { + ctx.Request.Header.Set("Content-Type", bodyContentType) + } + // trigger the session middleware on the context store := memstore.NewStore(make([]byte, 32), make([]byte, 32)) store.Options(router.SessionOptions()) diff --git a/internal/api/client/auth/authorize.go b/internal/api/client/auth/authorize.go index 6f96484a8..233dacfd2 100644 --- a/internal/api/client/auth/authorize.go +++ b/internal/api/client/auth/authorize.go @@ -246,7 +246,7 @@ func (m *Module) AuthorizePOSTHandler(c *gin.Context) { sessionUserID: {userID}, } - if err := m.server.HandleAuthorizeRequest(c.Writer, c.Request); err != nil { + if err := m.processor.OAuthHandleAuthorizeRequest(c.Writer, c.Request); err != nil { api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error(), helpfulAdvice), m.processor.InstanceGet) } } diff --git a/internal/api/client/auth/authorize_test.go b/internal/api/client/auth/authorize_test.go index f9c1eceb6..35b995e70 100644 --- a/internal/api/client/auth/authorize_test.go +++ b/internal/api/client/auth/authorize_test.go @@ -69,7 +69,7 @@ func (suite *AuthAuthorizeTestSuite) TestAccountAuthorizeHandler() { } doTest := func(testCase authorizeHandlerTestCase) { - ctx, recorder := suite.newContext(http.MethodGet, auth.OauthAuthorizePath) + ctx, recorder := suite.newContext(http.MethodGet, auth.OauthAuthorizePath, nil, "") user := suite.testUsers["unconfirmed_account"] account := suite.testAccounts["unconfirmed_account"] diff --git a/internal/api/client/auth/token.go b/internal/api/client/auth/token.go index 34fb62940..fbbd08404 100644 --- a/internal/api/client/auth/token.go +++ b/internal/api/client/auth/token.go @@ -19,20 +19,22 @@ package auth import ( + "net/http" "net/url" "github.com/superseriousbusiness/gotosocial/internal/api" "github.com/superseriousbusiness/gotosocial/internal/gtserror" + "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/gin-gonic/gin" ) -type tokenBody struct { +type tokenRequestForm struct { + GrantType *string `form:"grant_type" json:"grant_type" xml:"grant_type"` + Code *string `form:"code" json:"code" xml:"code"` + RedirectURI *string `form:"redirect_uri" json:"redirect_uri" xml:"redirect_uri"` 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"` Scope *string `form:"scope" json:"scope" xml:"scope"` } @@ -44,35 +46,70 @@ func (m *Module) TokenPOSTHandler(c *gin.Context) { return } - form := &tokenBody{} + help := []string{} + + form := &tokenRequestForm{} if err := c.ShouldBind(form); err != nil { - api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, helpfulAdvice), m.processor.InstanceGet) + api.OAuthErrorHandler(c, gtserror.NewErrorBadRequest(oauth.InvalidRequest(), err.Error())) return } c.Request.Form = url.Values{} + + var grantType string + if form.GrantType != nil { + grantType = *form.GrantType + c.Request.Form.Set("grant_type", grantType) + } else { + help = append(help, "grant_type was not set in the token request form, but must be set to authorization_code or client_credentials") + } + if form.ClientID != nil { c.Request.Form.Set("client_id", *form.ClientID) + } else { + help = append(help, "client_id was not set in the token request form") } + if form.ClientSecret != nil { c.Request.Form.Set("client_secret", *form.ClientSecret) + } else { + help = append(help, "client_secret was not set in the token request form") } - 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) + } else { + help = append(help, "redirect_uri was not set in the token request form") } + + var code string + if form.Code != nil { + if grantType != "authorization_code" { + help = append(help, "a code was provided in the token request form, but grant_type was not set to authorization_code") + } else { + code = *form.Code + c.Request.Form.Set("code", code) + } + } else if grantType == "authorization_code" { + help = append(help, "code was not set in the token request form, but must be set since grant_type is authorization_code") + } + if form.Scope != nil { c.Request.Form.Set("scope", *form.Scope) } - // pass the writer and request into the oauth server handler, which will - // take care of writing the oauth token into the response etc - if err := m.server.HandleTokenRequest(c.Writer, c.Request); err != nil { - api.ErrorHandler(c, gtserror.NewErrorInternalError(err, helpfulAdvice), m.processor.InstanceGet) + if len(help) != 0 { + api.OAuthErrorHandler(c, gtserror.NewErrorBadRequest(oauth.InvalidRequest(), help...)) + return } + + token, errWithCode := m.processor.OAuthHandleTokenRequest(c.Request) + if errWithCode != nil { + api.OAuthErrorHandler(c, errWithCode) + return + } + + c.Header("Cache-Control", "no-store") + c.Header("Pragma", "no-cache") + c.JSON(http.StatusOK, token) } diff --git a/internal/api/client/auth/token_test.go b/internal/api/client/auth/token_test.go new file mode 100644 index 000000000..50bbd6918 --- /dev/null +++ b/internal/api/client/auth/token_test.go @@ -0,0 +1,215 @@ +/* + GoToSocial + Copyright (C) 2021-2022 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 . +*/ + +package auth_test + +import ( + "context" + "encoding/json" + "io/ioutil" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/suite" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/testrig" +) + +type TokenTestSuite struct { + AuthStandardTestSuite +} + +func (suite *TokenTestSuite) TestPOSTTokenEmptyForm() { + ctx, recorder := suite.newContext(http.MethodPost, "oauth/token", []byte{}, "") + ctx.Request.Header.Set("accept", "application/json") + + suite.authModule.TokenPOSTHandler(ctx) + + suite.Equal(http.StatusBadRequest, recorder.Code) + + result := recorder.Result() + defer result.Body.Close() + + b, err := ioutil.ReadAll(result.Body) + suite.NoError(err) + + suite.Equal(`{"error":"invalid_request","error_description":"Bad Request: grant_type was not set in the token request form, but must be set to authorization_code or client_credentials: client_id was not set in the token request form: client_secret was not set in the token request form: redirect_uri was not set in the token request form"}`, string(b)) +} + +func (suite *TokenTestSuite) TestRetrieveClientCredentialsOK() { + testClient := suite.testClients["local_account_1"] + + requestBody, w, err := testrig.CreateMultipartFormData( + "", "", + map[string]string{ + "grant_type": "client_credentials", + "client_id": testClient.ID, + "client_secret": testClient.Secret, + "redirect_uri": "http://localhost:8080", + }) + if err != nil { + panic(err) + } + bodyBytes := requestBody.Bytes() + + ctx, recorder := suite.newContext(http.MethodPost, "oauth/token", bodyBytes, w.FormDataContentType()) + ctx.Request.Header.Set("accept", "application/json") + + suite.authModule.TokenPOSTHandler(ctx) + + suite.Equal(http.StatusOK, recorder.Code) + + result := recorder.Result() + defer result.Body.Close() + + b, err := ioutil.ReadAll(result.Body) + suite.NoError(err) + + t := &apimodel.Token{} + err = json.Unmarshal(b, t) + suite.NoError(err) + + suite.Equal("Bearer", t.TokenType) + suite.NotEmpty(t.AccessToken) + suite.NotEmpty(t.CreatedAt) + suite.WithinDuration(time.Now(), time.Unix(t.CreatedAt, 0), 1*time.Minute) + + // there should be a token in the database now too + dbToken := >smodel.Token{} + err = suite.db.GetWhere(context.Background(), []db.Where{{Key: "access", Value: t.AccessToken}}, dbToken) + suite.NoError(err) + suite.NotNil(dbToken) +} + +func (suite *TokenTestSuite) TestRetrieveAuthorizationCodeOK() { + testClient := suite.testClients["local_account_1"] + testUserAuthorizationToken := suite.testTokens["local_account_1_user_authorization_token"] + + requestBody, w, err := testrig.CreateMultipartFormData( + "", "", + map[string]string{ + "grant_type": "authorization_code", + "client_id": testClient.ID, + "client_secret": testClient.Secret, + "redirect_uri": "http://localhost:8080", + "code": testUserAuthorizationToken.Code, + }) + if err != nil { + panic(err) + } + bodyBytes := requestBody.Bytes() + + ctx, recorder := suite.newContext(http.MethodPost, "oauth/token", bodyBytes, w.FormDataContentType()) + ctx.Request.Header.Set("accept", "application/json") + + suite.authModule.TokenPOSTHandler(ctx) + + suite.Equal(http.StatusOK, recorder.Code) + + result := recorder.Result() + defer result.Body.Close() + + b, err := ioutil.ReadAll(result.Body) + suite.NoError(err) + + t := &apimodel.Token{} + err = json.Unmarshal(b, t) + suite.NoError(err) + + suite.Equal("Bearer", t.TokenType) + suite.NotEmpty(t.AccessToken) + suite.NotEmpty(t.CreatedAt) + suite.WithinDuration(time.Now(), time.Unix(t.CreatedAt, 0), 1*time.Minute) + + dbToken := >smodel.Token{} + err = suite.db.GetWhere(context.Background(), []db.Where{{Key: "access", Value: t.AccessToken}}, dbToken) + suite.NoError(err) + suite.NotNil(dbToken) +} + +func (suite *TokenTestSuite) TestRetrieveAuthorizationCodeNoCode() { + testClient := suite.testClients["local_account_1"] + + requestBody, w, err := testrig.CreateMultipartFormData( + "", "", + map[string]string{ + "grant_type": "authorization_code", + "client_id": testClient.ID, + "client_secret": testClient.Secret, + "redirect_uri": "http://localhost:8080", + }) + if err != nil { + panic(err) + } + bodyBytes := requestBody.Bytes() + + ctx, recorder := suite.newContext(http.MethodPost, "oauth/token", bodyBytes, w.FormDataContentType()) + ctx.Request.Header.Set("accept", "application/json") + + suite.authModule.TokenPOSTHandler(ctx) + + suite.Equal(http.StatusBadRequest, recorder.Code) + + result := recorder.Result() + defer result.Body.Close() + + b, err := ioutil.ReadAll(result.Body) + suite.NoError(err) + + suite.Equal(`{"error":"invalid_request","error_description":"Bad Request: code was not set in the token request form, but must be set since grant_type is authorization_code"}`, string(b)) +} + +func (suite *TokenTestSuite) TestRetrieveAuthorizationCodeWrongGrantType() { + testClient := suite.testClients["local_account_1"] + + requestBody, w, err := testrig.CreateMultipartFormData( + "", "", + map[string]string{ + "grant_type": "client_credentials", + "client_id": testClient.ID, + "client_secret": testClient.Secret, + "redirect_uri": "http://localhost:8080", + "code": "peepeepoopoo", + }) + if err != nil { + panic(err) + } + bodyBytes := requestBody.Bytes() + + ctx, recorder := suite.newContext(http.MethodPost, "oauth/token", bodyBytes, w.FormDataContentType()) + ctx.Request.Header.Set("accept", "application/json") + + suite.authModule.TokenPOSTHandler(ctx) + + suite.Equal(http.StatusBadRequest, recorder.Code) + + result := recorder.Result() + defer result.Body.Close() + + b, err := ioutil.ReadAll(result.Body) + suite.NoError(err) + + suite.Equal(`{"error":"invalid_request","error_description":"Bad Request: a code was provided in the token request form, but grant_type was not set to authorization_code"}`, string(b)) +} + +func TestTokenTestSuite(t *testing.T) { + suite.Run(t, &TokenTestSuite{}) +} diff --git a/internal/api/errorhandling.go b/internal/api/errorhandling.go index 57659f83c..59b58bcc3 100644 --- a/internal/api/errorhandling.go +++ b/internal/api/errorhandling.go @@ -125,3 +125,30 @@ func ErrorHandler(c *gin.Context, errWithCode gtserror.WithCode, instanceGet fun genericErrorHandler(c, instanceGet, accept, errWithCode) } } + +// OAuthErrorHandler is a lot like ErrorHandler, but it specifically returns errors +// that are compatible with https://datatracker.ietf.org/doc/html/rfc6749#section-5.2, +// but serializing errWithCode.Error() in the 'error' field, and putting any help text +// from the error in the 'error_description' field. This means you should be careful not +// to pass any detailed errors (that might contain sensitive information) into the +// errWithCode.Error() field, since the client will see this. Use your noggin! +func OAuthErrorHandler(c *gin.Context, errWithCode gtserror.WithCode) { + l := logrus.WithFields(logrus.Fields{ + "path": c.Request.URL.Path, + "error": errWithCode.Error(), + "help": errWithCode.Safe(), + }) + + statusCode := errWithCode.Code() + + if statusCode == http.StatusInternalServerError { + l.Error("Internal Server Error") + } else { + l.Debug("handling OAuth error") + } + + c.JSON(statusCode, gin.H{ + "error": errWithCode.Error(), + "error_description": errWithCode.Safe(), + }) +} diff --git a/internal/oauth/errors.go b/internal/oauth/errors.go new file mode 100644 index 000000000..25278bdc9 --- /dev/null +++ b/internal/oauth/errors.go @@ -0,0 +1,26 @@ +/* + GoToSocial + Copyright (C) 2021-2022 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 . +*/ + +package oauth + +import "github.com/superseriousbusiness/oauth2/v4/errors" + +// InvalidRequest returns an oauth spec compliant 'invalid_request' error. +func InvalidRequest() error { + return errors.New("invalid_request") +} diff --git a/internal/oauth/server.go b/internal/oauth/server.go index bfe615832..4dcc41ceb 100644 --- a/internal/oauth/server.go +++ b/internal/oauth/server.go @@ -25,6 +25,7 @@ "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/oauth2/v4" "github.com/superseriousbusiness/oauth2/v4/errors" "github.com/superseriousbusiness/oauth2/v4/manage" @@ -52,7 +53,7 @@ // Server wraps some oauth2 server functions in an interface, exposing only what is needed type Server interface { - HandleTokenRequest(w http.ResponseWriter, r *http.Request) error + HandleTokenRequest(r *http.Request) (map[string]interface{}, gtserror.WithCode) HandleAuthorizeRequest(w http.ResponseWriter, r *http.Request) error ValidationBearerToken(r *http.Request) (oauth2.TokenInfo, error) GenerateUserAccessToken(ctx context.Context, ti oauth2.TokenInfo, clientSecret string, userID string) (accessToken oauth2.TokenInfo, err error) @@ -116,8 +117,25 @@ func New(ctx context.Context, database db.Basic) Server { } // HandleTokenRequest wraps the oauth2 library's HandleTokenRequest function -func (s *s) HandleTokenRequest(w http.ResponseWriter, r *http.Request) error { - return s.server.HandleTokenRequest(w, r) +func (s *s) HandleTokenRequest(r *http.Request) (map[string]interface{}, gtserror.WithCode) { + ctx := r.Context() + + gt, tgr, err := s.server.ValidationTokenRequest(r) + if err != nil { + help := fmt.Sprintf("could not validate token request: %s", err) + return nil, gtserror.NewErrorBadRequest(err, help) + } + + ti, err := s.server.GetAccessToken(ctx, gt, tgr) + if err != nil { + help := fmt.Sprintf("could not get access token: %s", err) + return nil, gtserror.NewErrorBadRequest(err, help) + } + + data := s.server.GetTokenData(ti) + data["created_at"] = ti.GetAccessCreateAt().Unix() + + return data, nil } // HandleAuthorizeRequest wraps the oauth2 library's HandleAuthorizeRequest function diff --git a/internal/processing/oauth.go b/internal/processing/oauth.go new file mode 100644 index 000000000..9c974f76e --- /dev/null +++ b/internal/processing/oauth.go @@ -0,0 +1,35 @@ +/* + GoToSocial + Copyright (C) 2021-2022 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 . +*/ + +package processing + +import ( + "net/http" + + "github.com/superseriousbusiness/gotosocial/internal/gtserror" +) + +func (p *processor) OAuthHandleAuthorizeRequest(w http.ResponseWriter, r *http.Request) error { + // todo: some kind of metrics stuff here + return p.oauthServer.HandleAuthorizeRequest(w, r) +} + +func (p *processor) OAuthHandleTokenRequest(r *http.Request) (map[string]interface{}, gtserror.WithCode) { + // todo: some kind of metrics stuff here + return p.oauthServer.HandleTokenRequest(r) +} diff --git a/internal/processing/processor.go b/internal/processing/processor.go index a7a1c22e0..3afc25196 100644 --- a/internal/processing/processor.go +++ b/internal/processing/processor.go @@ -152,6 +152,9 @@ type Processor interface { // NotificationsGet NotificationsGet(ctx context.Context, authed *oauth.Auth, limit int, maxID string, sinceID string) (*apimodel.TimelineResponse, gtserror.WithCode) + OAuthHandleTokenRequest(r *http.Request) (map[string]interface{}, gtserror.WithCode) + OAuthHandleAuthorizeRequest(w http.ResponseWriter, r *http.Request) error + // SearchGet performs a search with the given params, resolving/dereferencing remotely as desired SearchGet(ctx context.Context, authed *oauth.Auth, searchQuery *apimodel.SearchQuery) (*apimodel.SearchResult, gtserror.WithCode) diff --git a/testrig/testmodels.go b/testrig/testmodels.go index ee601e053..b74ada751 100644 --- a/testrig/testmodels.go +++ b/testrig/testmodels.go @@ -56,6 +56,23 @@ func NewTestTokens() map[string]*gtsmodel.Token { AccessCreateAt: time.Now(), AccessExpiresAt: time.Now().Add(72 * time.Hour), }, + "local_account_1_client_application_token": { + ID: "01P9SVWS9J3SPHZQ3KCMBEN70N", + ClientID: "01F8MGV8AC3NGSJW0FE8W1BV70", + RedirectURI: "http://localhost:8080", + Access: "ZTK1MWMWZDGTMGMXOS0ZY2UXLWI5ZWETMWEZYZZIYTLHMZI4", + AccessCreateAt: TimeMustParse("2022-06-10T15:22:08Z"), + AccessExpiresAt: TimeMustParse("2050-01-01T15:22:08Z"), + }, + "local_account_1_user_authorization_token": { + ID: "01G574M2VTV66YZBC9AZ7HY3FV", + ClientID: "01F8MGV8AC3NGSJW0FE8W1BV70", + UserID: "01F8MGVGPHQ2D3P3X0454H54Z5", + RedirectURI: "http://localhost:8080", + Code: "ZJYYMZQ0MTQTZTU1NC0ZNJK4LWE2ZWITYTM1MDHHOTAXNJHL", + CodeCreateAt: TimeMustParse("2022-06-10T15:22:08Z"), + CodeExpiresAt: TimeMustParse("2050-01-01T15:22:08Z"), + }, "local_account_2": { ID: "01F8MGVVM1EDVYET710J27XY5R", ClientID: "01F8MGW47HN8ZXNHNZ7E47CDMQ",