diff --git a/go.mod b/go.mod index 747c5bd..178d8bd 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/gotosocial/gotosocial go 1.16 require ( + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/gin-contrib/sessions v0.0.3 github.com/gin-gonic/gin v1.6.3 github.com/go-fed/activity v1.0.0 @@ -11,6 +12,8 @@ require ( github.com/golang/mock v1.4.4 // indirect github.com/google/uuid v1.2.0 github.com/gotosocial/oauth2/v4 v4.2.1-0.20210316171520-7b12112bbb88 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/onsi/ginkgo v1.15.0 // indirect github.com/onsi/gomega v1.10.5 // indirect github.com/sirupsen/logrus v1.8.0 diff --git a/go.sum b/go.sum index 077380e..d10e242 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,9 @@ github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQ github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/dave/jennifer v1.3.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -136,10 +137,12 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= diff --git a/internal/db/model/user.go b/internal/db/model/user.go index 24de1cc..111ee9e 100644 --- a/internal/db/model/user.go +++ b/internal/db/model/user.go @@ -33,7 +33,7 @@ type User struct { // id of this user in the local database; the end-user will never need to know this, it's strictly internal ID string `pg:"type:uuid,default:gen_random_uuid(),pk,notnull,unique"` // confirmed email address for this user, this should be unique -- only one email address registered per instance, multiple users per email are not supported - Email string `pg:",notnull,unique"` + Email string `pg:"default:'',notnull,unique"` // The id of the local gtsmodel.Account entry for this user, if it exists (unconfirmed users don't have an account yet) AccountID string `pg:"default:'',notnull,unique"` // The encrypted password of this user, generated using https://pkg.go.dev/golang.org/x/crypto/bcrypt#GenerateFromPassword. A salt is included so we're safe against 🌈 tables diff --git a/internal/db/pg.go b/internal/db/pg.go index bc0cc05..b694c45 100644 --- a/internal/db/pg.go +++ b/internal/db/pg.go @@ -379,7 +379,7 @@ func (ps *postgresService) IsUsernameAvailable(username string) error { // if no error we fail because it means we found something // if error but it's not pg.ErrNoRows then we fail // if err is pg.ErrNoRows we're good, we found nothing so continue - if err := ps.conn.Model(&model.Account{}).Where("username = ?").Where("domain = ?", nil).Select(); err == nil { + if err := ps.conn.Model(&model.Account{}).Where("username = ?", username).Where("domain = ?", nil).Select(); err == nil { return fmt.Errorf("username %s already in use", username) } else if err != pg.ErrNoRows { return fmt.Errorf("db error: %s", err) @@ -404,8 +404,8 @@ func (ps *postgresService) IsEmailAvailable(email string) error { return fmt.Errorf("db error: %s", err) } - // check if this email is associated with an account already - if err := ps.conn.Model(&model.Account{}).Where("email = ?", email).WhereOr("unconfirmed_email = ?", email).Select(); err == nil { + // check if this email is associated with a user already + if err := ps.conn.Model(&model.User{}).Where("email = ?", email).WhereOr("unconfirmed_email = ?", email).Select(); err == nil { // fail because we found something return fmt.Errorf("email %s already in use", email) } else if err != pg.ErrNoRows { diff --git a/internal/module/account/account.go b/internal/module/account/account.go index 8276a01..cc25bcd 100644 --- a/internal/module/account/account.go +++ b/internal/module/account/account.go @@ -69,7 +69,7 @@ func (m *accountModule) Route(r router.Router) error { // It should be served as a POST at /api/v1/accounts func (m *accountModule) accountCreatePOSTHandler(c *gin.Context) { l := m.log.WithField("func", "accountCreatePOSTHandler") - authed, err := oauth.GetAuthed(c) + authed, err := oauth.MustAuthed(c, true, true, false, false) if err != nil { l.Debugf("couldn't auth: %s", err) c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) @@ -167,6 +167,7 @@ func (m *accountModule) accountCreate(form *mastotypes.AccountCreateRequest, sig } l.Tracef("generating a token for user %s with account %s and application %s", user.ID, user.AccountID, app.ID) + fmt.Printf("ACCOUNT CREATE\n\n%+v\n\n%+v\n\n%+v\n", token, app, user) ti, err := m.oauthServer.GenerateUserAccessToken(token, app.ClientSecret, user.ID) if err != nil { return nil, fmt.Errorf("error creating new access token for user %s: %s", user.ID, err) diff --git a/internal/module/account/account_test.go b/internal/module/account/account_test.go index 2d9076d..0292ce1 100644 --- a/internal/module/account/account_test.go +++ b/internal/module/account/account_test.go @@ -20,13 +20,19 @@ package account import ( "context" + "net/http" "net/http/httptest" + "net/url" "testing" + "time" "github.com/gin-gonic/gin" "github.com/gotosocial/gotosocial/internal/config" "github.com/gotosocial/gotosocial/internal/db" "github.com/gotosocial/gotosocial/internal/db/model" + "github.com/gotosocial/gotosocial/internal/oauth" + "github.com/gotosocial/oauth2/v4" + oauthmodels "github.com/gotosocial/oauth2/v4/models" "github.com/sirupsen/logrus" "github.com/stretchr/testify/suite" ) @@ -37,6 +43,8 @@ type AccountTestSuite struct { testAccountLocal *model.Account testAccountRemote *model.Account testUser *model.User + testApplication *model.Application + testToken oauth2.TokenInfo db db.DB accountModule *accountModule } @@ -57,6 +65,11 @@ func (suite *AccountTestSuite) SetupSuite() { Database: "postgres", ApplicationName: "gotosocial", } + c.AccountsConfig = &config.AccountsConfig{ + OpenRegistration: true, + RequireApproval: true, + ReasonRequired: true, + } database, err := db.New(context.Background(), c, log) if err != nil { @@ -70,6 +83,26 @@ func (suite *AccountTestSuite) SetupSuite() { log: log, } + suite.testApplication = &model.Application{ + ID: "weeweeeeeeeeeeeeee", + 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: "aaaaaa-aaaaaaaa-aaaaaaaaaaa", + } + + suite.testToken = &oauthmodels.Token{ + ClientID: "a-known-client-id", + RedirectURI: "http://localhost:8080", + Scope: "read", + Code: "123456789", + CodeCreateAt: time.Now(), + CodeExpiresIn: time.Duration(10 * time.Minute), + } + // encryptedPassword, err := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost) // if err != nil { // logrus.Panicf("error encrypting user pass: %s", err) @@ -177,6 +210,7 @@ func (suite *AccountTestSuite) SetupTest() { &model.Follow{}, &model.Status{}, &model.Application{}, + &model.EmailDomainBlock{}, } for _, m := range models { @@ -194,6 +228,7 @@ func (suite *AccountTestSuite) TearDownTest() { &model.Follow{}, &model.Status{}, &model.Application{}, + &model.EmailDomainBlock{}, } for _, m := range models { if err := suite.db.DropTable(m); err != nil { @@ -206,8 +241,19 @@ func (suite *AccountTestSuite) TestAccountCreatePOSTHandler() { // TODO: figure out how to test this properly recorder := httptest.NewRecorder() recorder.Header().Set("X-Forwarded-For", "127.0.0.1") + recorder.Header().Set("Content-Type", "application/json") ctx, _ := gin.CreateTestContext(recorder) - // ctx.Set() + ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplication) + ctx.Set(oauth.SessionAuthorizedToken, suite.testToken) + ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v1/accounts", nil) + ctx.Request.Form = url.Values{ + "reason": []string{"a very good reason that's at least 40 characters i swear"}, + "username": []string{"test_user"}, + "email": []string{"user@example.org"}, + "password": []string{"very-strong-password"}, + "agreement": []string{"true"}, + "locale": []string{"en"}, + } suite.accountModule.accountCreatePOSTHandler(ctx) } diff --git a/internal/oauth/oauth.go b/internal/oauth/oauth.go index f7d2b57..5a7e3e4 100644 --- a/internal/oauth/oauth.go +++ b/internal/oauth/oauth.go @@ -20,6 +20,7 @@ package oauth import ( "context" + "fmt" "net/http" "github.com/gin-gonic/gin" @@ -127,6 +128,27 @@ func GetAuthed(c *gin.Context) (*Authed, error) { return a, nil } +// MustAuthed is like GetAuthed, but will fail if one of the requirements is not met. +func MustAuthed(c *gin.Context, requireToken bool, requireApp bool, requireUser bool, requireAccount bool) (*Authed, error) { + a, err := GetAuthed(c) + if err != nil { + return nil, err + } + if requireToken && a.Token == nil { + return nil, errors.New("token not supplied") + } + if requireApp && a.Application == nil { + return nil, errors.New("application not supplied") + } + if requireUser && a.User == nil { + return nil, errors.New("user not supplied") + } + if requireAccount && a.Account == nil { + return nil, errors.New("account not supplied") + } + return a, nil +} + // 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) @@ -149,16 +171,16 @@ func (s *s) ValidationBearerToken(r *http.Request) (oauth2.TokenInfo, error) { // The ti parameter refers to an existing Application token that was used to make the upstream // request. This token needs to be validated and exist in database in order to create a new token. func (s *s) GenerateUserAccessToken(ti oauth2.TokenInfo, clientSecret string, userID string) (accessToken oauth2.TokenInfo, err error) { - + fmt.Printf("GENERATE USER ACCESS TOKEN %+v\n", ti) tgr := &oauth2.TokenGenerateRequest{ - ClientID: ti.GetClientID(), - ClientSecret: clientSecret, - UserID: userID, - RedirectURI: ti.GetRedirectURI(), - Scope: ti.GetScope(), - Code: ti.GetCode(), - CodeChallenge: ti.GetCodeChallenge(), - CodeChallengeMethod: ti.GetCodeChallengeMethod(), + ClientID: ti.GetClientID(), + ClientSecret: clientSecret, + UserID: userID, + RedirectURI: ti.GetRedirectURI(), + Scope: ti.GetScope(), + Code: ti.GetCode(), + // CodeChallenge: ti.GetCodeChallenge(), + // CodeChallengeMethod: ti.GetCodeChallengeMethod(), } return s.server.Manager.GenerateAccessToken(context.Background(), oauth2.AuthorizationCode, tgr)