From dafc3b5b92865b97be48456e02ad235f4c79cf4e Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Tue, 20 Apr 2021 18:14:23 +0200 Subject: linting + organizing --- internal/apimodule/auth/README.md | 5 - internal/apimodule/auth/auth.go | 37 ++++--- internal/apimodule/auth/auth_test.go | 166 ------------------------------ internal/apimodule/auth/authorize.go | 10 +- internal/apimodule/auth/middleware.go | 4 +- internal/apimodule/auth/signin.go | 17 +-- internal/apimodule/auth/test/auth_test.go | 166 ++++++++++++++++++++++++++++++ internal/apimodule/auth/token.go | 4 +- 8 files changed, 202 insertions(+), 207 deletions(-) delete mode 100644 internal/apimodule/auth/README.md delete mode 100644 internal/apimodule/auth/auth_test.go create mode 100644 internal/apimodule/auth/test/auth_test.go (limited to 'internal/apimodule/auth') diff --git a/internal/apimodule/auth/README.md b/internal/apimodule/auth/README.md deleted file mode 100644 index 96b2443c1..000000000 --- a/internal/apimodule/auth/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# auth - -This package provides uses the [GoToSocial oauth2](https://github.com/gotosocial/oauth2) module (forked from [go-oauth2](https://github.com/go-oauth2/oauth2)) to provide [oauth2](https://www.oauth.com/) functionality to the GoToSocial client API. - -It also provides a handler/middleware for attaching to the Gin engine for validating authenticated users. diff --git a/internal/apimodule/auth/auth.go b/internal/apimodule/auth/auth.go index b70adeb43..341805b40 100644 --- a/internal/apimodule/auth/auth.go +++ b/internal/apimodule/auth/auth.go @@ -16,12 +16,6 @@ along with this program. If not, see . */ -// Package auth is a module that provides oauth functionality to a router. -// It adds the following paths: -// /auth/sign_in -// /oauth/token -// /oauth/authorize -// It also includes the oauthTokenMiddleware, which can be attached to a router to authenticate every request by Bearer token. package auth import ( @@ -37,12 +31,16 @@ import ( ) const ( - authSignInPath = "/auth/sign_in" - oauthTokenPath = "/oauth/token" - oauthAuthorizePath = "/oauth/authorize" + // AuthSignInPath is the API path for users to sign in through + AuthSignInPath = "/auth/sign_in" + // OauthTokenPath is the API path to use for granting token requests to users with valid credentials + OauthTokenPath = "/oauth/token" + // OauthAuthorizePath is the API path for authorization requests (eg., authorize this app to act on my behalf as a user) + OauthAuthorizePath = "/oauth/authorize" ) -type authModule struct { +// Module implements the ClientAPIModule interface for +type Module struct { server oauth.Server db db.DB log *logrus.Logger @@ -50,7 +48,7 @@ type authModule struct { // New returns a new auth module func New(srv oauth.Server, db db.DB, log *logrus.Logger) apimodule.ClientAPIModule { - return &authModule{ + return &Module{ server: srv, db: db, log: log, @@ -58,20 +56,21 @@ func New(srv oauth.Server, db db.DB, log *logrus.Logger) apimodule.ClientAPIModu } // Route satisfies the RESTAPIModule interface -func (m *authModule) Route(s router.Router) error { - s.AttachHandler(http.MethodGet, authSignInPath, m.signInGETHandler) - s.AttachHandler(http.MethodPost, authSignInPath, m.signInPOSTHandler) +func (m *Module) Route(s router.Router) error { + s.AttachHandler(http.MethodGet, AuthSignInPath, m.SignInGETHandler) + s.AttachHandler(http.MethodPost, AuthSignInPath, m.SignInPOSTHandler) - s.AttachHandler(http.MethodPost, oauthTokenPath, m.tokenPOSTHandler) + s.AttachHandler(http.MethodPost, OauthTokenPath, m.TokenPOSTHandler) - s.AttachHandler(http.MethodGet, oauthAuthorizePath, m.authorizeGETHandler) - s.AttachHandler(http.MethodPost, oauthAuthorizePath, m.authorizePOSTHandler) + s.AttachHandler(http.MethodGet, OauthAuthorizePath, m.AuthorizeGETHandler) + s.AttachHandler(http.MethodPost, OauthAuthorizePath, m.AuthorizePOSTHandler) - s.AttachMiddleware(m.oauthTokenMiddleware) + s.AttachMiddleware(m.OauthTokenMiddleware) return nil } -func (m *authModule) CreateTables(db db.DB) error { +// CreateTables creates the necessary tables for this module in the given database +func (m *Module) CreateTables(db db.DB) error { models := []interface{}{ &oauth.Client{}, &oauth.Token{}, diff --git a/internal/apimodule/auth/auth_test.go b/internal/apimodule/auth/auth_test.go deleted file mode 100644 index 2c272e985..000000000 --- a/internal/apimodule/auth/auth_test.go +++ /dev/null @@ -1,166 +0,0 @@ -/* - 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 . -*/ - -package auth - -import ( - "context" - "fmt" - "testing" - - "github.com/google/uuid" - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/config" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/oauth" - "golang.org/x/crypto/bcrypt" -) - -type AuthTestSuite struct { - suite.Suite - oauthServer oauth.Server - db db.DB - testAccount *gtsmodel.Account - testApplication *gtsmodel.Application - testUser *gtsmodel.User - testClient *oauth.Client - config *config.Config -} - -// SetupSuite sets some variables on the suite that we can use as consts (more or less) throughout -func (suite *AuthTestSuite) SetupSuite() { - c := config.Empty() - // we're running on localhost without https so set the protocol to http - c.Protocol = "http" - // just for testing - c.Host = "localhost:8080" - // because go tests are run within the test package directory, we need to fiddle with the templateconfig - // basedir in a way that we wouldn't normally have to do when running the binary, in order to make - // the templates actually load - c.TemplateConfig.BaseDir = "../../../web/template/" - c.DBConfig = &config.DBConfig{ - Type: "postgres", - Address: "localhost", - Port: 5432, - User: "postgres", - Password: "postgres", - Database: "postgres", - ApplicationName: "gotosocial", - } - suite.config = c - - encryptedPassword, err := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost) - if err != nil { - logrus.Panicf("error encrypting user pass: %s", err) - } - - acctID := uuid.NewString() - - suite.testAccount = >smodel.Account{ - ID: acctID, - Username: "test_user", - } - suite.testUser = >smodel.User{ - EncryptedPassword: string(encryptedPassword), - Email: "user@example.org", - AccountID: acctID, - } - suite.testClient = &oauth.Client{ - ID: "a-known-client-id", - Secret: "some-secret", - Domain: fmt.Sprintf("%s://%s", c.Protocol, c.Host), - } - suite.testApplication = >smodel.Application{ - Name: "a test application", - Website: "https://some-application-website.com", - RedirectURI: "http://localhost:8080", - ClientID: "a-known-client-id", - ClientSecret: "some-secret", - Scopes: "read", - VapidKey: uuid.NewString(), - } -} - -// SetupTest creates a postgres connection and creates the oauth_clients table before each test -func (suite *AuthTestSuite) SetupTest() { - - log := logrus.New() - log.SetLevel(logrus.TraceLevel) - db, err := db.New(context.Background(), suite.config, log) - if err != nil { - logrus.Panicf("error creating database connection: %s", err) - } - - suite.db = db - - models := []interface{}{ - &oauth.Client{}, - &oauth.Token{}, - >smodel.User{}, - >smodel.Account{}, - >smodel.Application{}, - } - - for _, m := range models { - if err := suite.db.CreateTable(m); err != nil { - logrus.Panicf("db connection error: %s", err) - } - } - - suite.oauthServer = oauth.New(suite.db, log) - - if err := suite.db.Put(suite.testAccount); err != nil { - logrus.Panicf("could not insert test account into db: %s", err) - } - if err := suite.db.Put(suite.testUser); err != nil { - logrus.Panicf("could not insert test user into db: %s", err) - } - if err := suite.db.Put(suite.testClient); err != nil { - logrus.Panicf("could not insert test client into db: %s", err) - } - if err := suite.db.Put(suite.testApplication); err != nil { - logrus.Panicf("could not insert test application into db: %s", err) - } - -} - -// TearDownTest drops the oauth_clients table and closes the pg connection after each test -func (suite *AuthTestSuite) TearDownTest() { - models := []interface{}{ - &oauth.Client{}, - &oauth.Token{}, - >smodel.User{}, - >smodel.Account{}, - >smodel.Application{}, - } - for _, m := range models { - if err := suite.db.DropTable(m); err != nil { - logrus.Panicf("error dropping table: %s", err) - } - } - if err := suite.db.Stop(context.Background()); err != nil { - logrus.Panicf("error closing db connection: %s", err) - } - suite.db = nil -} - -func TestAuthTestSuite(t *testing.T) { - suite.Run(t, new(AuthTestSuite)) -} diff --git a/internal/apimodule/auth/authorize.go b/internal/apimodule/auth/authorize.go index bf525e09e..4bc1991ac 100644 --- a/internal/apimodule/auth/authorize.go +++ b/internal/apimodule/auth/authorize.go @@ -31,10 +31,10 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/mastotypes/mastomodel" ) -// authorizeGETHandler should be served as GET at https://example.org/oauth/authorize +// AuthorizeGETHandler should be served as GET at https://example.org/oauth/authorize // The idea here is to present an oauth authorize page to the user, with a button // that they have to click to accept. See here: https://docs.joinmastodon.org/methods/apps/oauth/#authorize-a-user -func (m *authModule) authorizeGETHandler(c *gin.Context) { +func (m *Module) AuthorizeGETHandler(c *gin.Context) { l := m.log.WithField("func", "AuthorizeGETHandler") s := sessions.Default(c) @@ -46,7 +46,7 @@ func (m *authModule) authorizeGETHandler(c *gin.Context) { if err := parseAuthForm(c, l); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } else { - c.Redirect(http.StatusFound, authSignInPath) + c.Redirect(http.StatusFound, AuthSignInPath) } return } @@ -108,11 +108,11 @@ func (m *authModule) authorizeGETHandler(c *gin.Context) { }) } -// authorizePOSTHandler should be served as POST at https://example.org/oauth/authorize +// AuthorizePOSTHandler should be served as POST at https://example.org/oauth/authorize // At this point we assume that the user has A) logged in and B) accepted that the app should act for them, // so we should proceed with the authentication flow and generate an oauth token for them if we can. // See here: https://docs.joinmastodon.org/methods/apps/oauth/#authorize-a-user -func (m *authModule) authorizePOSTHandler(c *gin.Context) { +func (m *Module) AuthorizePOSTHandler(c *gin.Context) { l := m.log.WithField("func", "AuthorizePOSTHandler") s := sessions.Default(c) diff --git a/internal/apimodule/auth/middleware.go b/internal/apimodule/auth/middleware.go index 4ca1f47a2..1d9a85993 100644 --- a/internal/apimodule/auth/middleware.go +++ b/internal/apimodule/auth/middleware.go @@ -24,12 +24,12 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/oauth" ) -// oauthTokenMiddleware checks if the client has presented a valid oauth Bearer token. +// OauthTokenMiddleware checks if the client has presented a valid oauth Bearer token. // If so, it will check the User that the token belongs to, and set that in the context of // the request. Then, it will look up the account for that user, and set that in the request too. // If user or account can't be found, then the handler won't *fail*, in case the server wants to allow // public requests that don't have a Bearer token set (eg., for public instance information and so on). -func (m *authModule) oauthTokenMiddleware(c *gin.Context) { +func (m *Module) OauthTokenMiddleware(c *gin.Context) { l := m.log.WithField("func", "ValidatePassword") l.Trace("entering OauthTokenMiddleware") diff --git a/internal/apimodule/auth/signin.go b/internal/apimodule/auth/signin.go index a6994c90e..44de0891c 100644 --- a/internal/apimodule/auth/signin.go +++ b/internal/apimodule/auth/signin.go @@ -28,23 +28,24 @@ import ( "golang.org/x/crypto/bcrypt" ) +// login just wraps a form-submitted username (we want an email) and password type login struct { Email string `form:"username"` Password string `form:"password"` } -// signInGETHandler should be served at https://example.org/auth/sign_in. +// SignInGETHandler should be served at https://example.org/auth/sign_in. // The idea is to present a sign in page to the user, where they can enter their username and password. // The form will then POST to the sign in page, which will be handled by SignInPOSTHandler -func (m *authModule) signInGETHandler(c *gin.Context) { +func (m *Module) SignInGETHandler(c *gin.Context) { m.log.WithField("func", "SignInGETHandler").Trace("serving sign in html") c.HTML(http.StatusOK, "sign-in.tmpl", gin.H{}) } -// signInPOSTHandler should be served at https://example.org/auth/sign_in. +// SignInPOSTHandler should be served at https://example.org/auth/sign_in. // The idea is to present a sign in page to the user, where they can enter their username and password. // The handler will then redirect to the auth handler served at /auth -func (m *authModule) signInPOSTHandler(c *gin.Context) { +func (m *Module) SignInPOSTHandler(c *gin.Context) { l := m.log.WithField("func", "SignInPOSTHandler") s := sessions.Default(c) form := &login{} @@ -54,7 +55,7 @@ func (m *authModule) signInPOSTHandler(c *gin.Context) { } l.Tracef("parsed form: %+v", form) - userid, err := m.validatePassword(form.Email, form.Password) + userid, err := m.ValidatePassword(form.Email, form.Password) if err != nil { c.String(http.StatusForbidden, err.Error()) return @@ -67,14 +68,14 @@ func (m *authModule) signInPOSTHandler(c *gin.Context) { } l.Trace("redirecting to auth page") - c.Redirect(http.StatusFound, oauthAuthorizePath) + c.Redirect(http.StatusFound, OauthAuthorizePath) } -// validatePassword takes an email address and a password. +// ValidatePassword takes an email address and a password. // The goal is to authenticate the password against the one for that email // address stored in the database. If OK, we return the userid (a uuid) for that user, // so that it can be used in further Oauth flows to generate a token/retreieve an oauth client from the db. -func (m *authModule) validatePassword(email string, password string) (userid string, err error) { +func (m *Module) ValidatePassword(email string, password string) (userid string, err error) { l := m.log.WithField("func", "ValidatePassword") // make sure an email/password was provided and bail if not diff --git a/internal/apimodule/auth/test/auth_test.go b/internal/apimodule/auth/test/auth_test.go new file mode 100644 index 000000000..2c272e985 --- /dev/null +++ b/internal/apimodule/auth/test/auth_test.go @@ -0,0 +1,166 @@ +/* + 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 . +*/ + +package auth + +import ( + "context" + "fmt" + "testing" + + "github.com/google/uuid" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/db/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/oauth" + "golang.org/x/crypto/bcrypt" +) + +type AuthTestSuite struct { + suite.Suite + oauthServer oauth.Server + db db.DB + testAccount *gtsmodel.Account + testApplication *gtsmodel.Application + testUser *gtsmodel.User + testClient *oauth.Client + config *config.Config +} + +// SetupSuite sets some variables on the suite that we can use as consts (more or less) throughout +func (suite *AuthTestSuite) SetupSuite() { + c := config.Empty() + // we're running on localhost without https so set the protocol to http + c.Protocol = "http" + // just for testing + c.Host = "localhost:8080" + // because go tests are run within the test package directory, we need to fiddle with the templateconfig + // basedir in a way that we wouldn't normally have to do when running the binary, in order to make + // the templates actually load + c.TemplateConfig.BaseDir = "../../../web/template/" + c.DBConfig = &config.DBConfig{ + Type: "postgres", + Address: "localhost", + Port: 5432, + User: "postgres", + Password: "postgres", + Database: "postgres", + ApplicationName: "gotosocial", + } + suite.config = c + + encryptedPassword, err := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost) + if err != nil { + logrus.Panicf("error encrypting user pass: %s", err) + } + + acctID := uuid.NewString() + + suite.testAccount = >smodel.Account{ + ID: acctID, + Username: "test_user", + } + suite.testUser = >smodel.User{ + EncryptedPassword: string(encryptedPassword), + Email: "user@example.org", + AccountID: acctID, + } + suite.testClient = &oauth.Client{ + ID: "a-known-client-id", + Secret: "some-secret", + Domain: fmt.Sprintf("%s://%s", c.Protocol, c.Host), + } + suite.testApplication = >smodel.Application{ + Name: "a test application", + Website: "https://some-application-website.com", + RedirectURI: "http://localhost:8080", + ClientID: "a-known-client-id", + ClientSecret: "some-secret", + Scopes: "read", + VapidKey: uuid.NewString(), + } +} + +// SetupTest creates a postgres connection and creates the oauth_clients table before each test +func (suite *AuthTestSuite) SetupTest() { + + log := logrus.New() + log.SetLevel(logrus.TraceLevel) + db, err := db.New(context.Background(), suite.config, log) + if err != nil { + logrus.Panicf("error creating database connection: %s", err) + } + + suite.db = db + + models := []interface{}{ + &oauth.Client{}, + &oauth.Token{}, + >smodel.User{}, + >smodel.Account{}, + >smodel.Application{}, + } + + for _, m := range models { + if err := suite.db.CreateTable(m); err != nil { + logrus.Panicf("db connection error: %s", err) + } + } + + suite.oauthServer = oauth.New(suite.db, log) + + if err := suite.db.Put(suite.testAccount); err != nil { + logrus.Panicf("could not insert test account into db: %s", err) + } + if err := suite.db.Put(suite.testUser); err != nil { + logrus.Panicf("could not insert test user into db: %s", err) + } + if err := suite.db.Put(suite.testClient); err != nil { + logrus.Panicf("could not insert test client into db: %s", err) + } + if err := suite.db.Put(suite.testApplication); err != nil { + logrus.Panicf("could not insert test application into db: %s", err) + } + +} + +// TearDownTest drops the oauth_clients table and closes the pg connection after each test +func (suite *AuthTestSuite) TearDownTest() { + models := []interface{}{ + &oauth.Client{}, + &oauth.Token{}, + >smodel.User{}, + >smodel.Account{}, + >smodel.Application{}, + } + for _, m := range models { + if err := suite.db.DropTable(m); err != nil { + logrus.Panicf("error dropping table: %s", err) + } + } + if err := suite.db.Stop(context.Background()); err != nil { + logrus.Panicf("error closing db connection: %s", err) + } + suite.db = nil +} + +func TestAuthTestSuite(t *testing.T) { + suite.Run(t, new(AuthTestSuite)) +} diff --git a/internal/apimodule/auth/token.go b/internal/apimodule/auth/token.go index 1e54b6ab3..c531a3009 100644 --- a/internal/apimodule/auth/token.go +++ b/internal/apimodule/auth/token.go @@ -24,10 +24,10 @@ import ( "github.com/gin-gonic/gin" ) -// tokenPOSTHandler should be served as a POST at https://example.org/oauth/token +// 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 *authModule) tokenPOSTHandler(c *gin.Context) { +func (m *Module) TokenPOSTHandler(c *gin.Context) { l := m.log.WithField("func", "TokenPOSTHandler") l.Trace("entered TokenPOSTHandler") if err := m.server.HandleTokenRequest(c.Writer, c.Request); err != nil { -- cgit v1.2.3