groundwork for other account routes

This commit is contained in:
tsmethurst 2021-03-24 17:34:41 +01:00
parent 0ea69345b9
commit 26c482cd86
9 changed files with 186 additions and 65 deletions

View File

@ -111,10 +111,18 @@ func main() {
// TEMPLATE FLAGS // TEMPLATE FLAGS
&cli.StringFlag{ &cli.StringFlag{
Name: flagNames.TemplateBaseDir, Name: flagNames.TemplateBaseDir,
Usage: "Basedir for html templating files for rendering pages and composing emails", Usage: "Basedir for html templating files for rendering pages and composing emails.",
Value: "./web/template/", Value: "./web/template/",
EnvVars: []string{envNames.TemplateBaseDir}, EnvVars: []string{envNames.TemplateBaseDir},
}, },
// ACCOUNTS FLAGS
&cli.BoolFlag{
Name: flagNames.AccountsOpenRegistration,
Usage: "Allow anyone to submit an account signup request. If false, server will be invite-only.",
Value: false,
EnvVars: []string{envNames.AccountsOpenRegistration},
},
}, },
Commands: []*cli.Command{ Commands: []*cli.Command{
{ {

View File

@ -0,0 +1,24 @@
/*
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 config
type AccountsConfig struct {
// Do we want people to be able to just submit sign up requests, or do we want invite only?
OpenRegistration bool
}

View File

@ -33,6 +33,7 @@ type Config struct {
Protocol string `yaml:"protocol"` Protocol string `yaml:"protocol"`
DBConfig *DBConfig `yaml:"db"` DBConfig *DBConfig `yaml:"db"`
TemplateConfig *TemplateConfig `yaml:"template"` TemplateConfig *TemplateConfig `yaml:"template"`
AccountsConfig *AccountsConfig `yaml:"accounts"`
} }
// FromFile returns a new config from a file, or an error if something goes amiss. // FromFile returns a new config from a file, or an error if something goes amiss.
@ -136,11 +137,17 @@ func (c *Config) ParseCLIFlags(f KeyedFlags) {
if c.TemplateConfig.BaseDir == "" || f.IsSet(fn.TemplateBaseDir) { if c.TemplateConfig.BaseDir == "" || f.IsSet(fn.TemplateBaseDir) {
c.TemplateConfig.BaseDir = f.String(fn.TemplateBaseDir) c.TemplateConfig.BaseDir = f.String(fn.TemplateBaseDir)
} }
// accounts flags
if f.IsSet(fn.AccountsOpenRegistration) {
c.AccountsConfig.OpenRegistration = f.Bool(fn.AccountsOpenRegistration)
}
} }
// KeyedFlags is a wrapper for any type that can store keyed flags and give them back. // KeyedFlags is a wrapper for any type that can store keyed flags and give them back.
// HINT: This works with a urfave cli context struct ;) // HINT: This works with a urfave cli context struct ;)
type KeyedFlags interface { type KeyedFlags interface {
Bool(k string) bool
String(k string) string String(k string) string
Int(k string) int Int(k string) int
IsSet(k string) bool IsSet(k string) bool
@ -149,36 +156,38 @@ type KeyedFlags interface {
// Flags is used for storing the names of the various flags used for // Flags is used for storing the names of the various flags used for
// initializing and storing urfavecli flag variables. // initializing and storing urfavecli flag variables.
type Flags struct { type Flags struct {
LogLevel string LogLevel string
ApplicationName string ApplicationName string
ConfigPath string ConfigPath string
Host string Host string
Protocol string Protocol string
DbType string DbType string
DbAddress string DbAddress string
DbPort string DbPort string
DbUser string DbUser string
DbPassword string DbPassword string
DbDatabase string DbDatabase string
TemplateBaseDir string TemplateBaseDir string
AccountsOpenRegistration string
} }
// GetFlagNames returns a struct containing the names of the various flags used for // GetFlagNames returns a struct containing the names of the various flags used for
// initializing and storing urfavecli flag variables. // initializing and storing urfavecli flag variables.
func GetFlagNames() Flags { func GetFlagNames() Flags {
return Flags{ return Flags{
LogLevel: "log-level", LogLevel: "log-level",
ApplicationName: "application-name", ApplicationName: "application-name",
ConfigPath: "config-path", ConfigPath: "config-path",
Host: "host", Host: "host",
Protocol: "protocol", Protocol: "protocol",
DbType: "db-type", DbType: "db-type",
DbAddress: "db-address", DbAddress: "db-address",
DbPort: "db-port", DbPort: "db-port",
DbUser: "db-user", DbUser: "db-user",
DbPassword: "db-password", DbPassword: "db-password",
DbDatabase: "db-database", DbDatabase: "db-database",
TemplateBaseDir: "template-basedir", TemplateBaseDir: "template-basedir",
AccountsOpenRegistration: "accounts-open-registration",
} }
} }
@ -186,17 +195,18 @@ func GetFlagNames() Flags {
// initializing and storing urfavecli flag variables. // initializing and storing urfavecli flag variables.
func GetEnvNames() Flags { func GetEnvNames() Flags {
return Flags{ return Flags{
LogLevel: "GTS_LOG_LEVEL", LogLevel: "GTS_LOG_LEVEL",
ApplicationName: "GTS_APPLICATION_NAME", ApplicationName: "GTS_APPLICATION_NAME",
ConfigPath: "GTS_CONFIG_PATH", ConfigPath: "GTS_CONFIG_PATH",
Host: "GTS_HOST", Host: "GTS_HOST",
Protocol: "GTS_PROTOCOL", Protocol: "GTS_PROTOCOL",
DbType: "GTS_DB_TYPE", DbType: "GTS_DB_TYPE",
DbAddress: "GTS_DB_ADDRESS", DbAddress: "GTS_DB_ADDRESS",
DbPort: "GTS_DB_PORT", DbPort: "GTS_DB_PORT",
DbUser: "GTS_DB_USER", DbUser: "GTS_DB_USER",
DbPassword: "GTS_DB_PASSWORD", DbPassword: "GTS_DB_PASSWORD",
DbDatabase: "GTS_DB_DATABASE", DbDatabase: "GTS_DB_DATABASE",
TemplateBaseDir: "GTS_TEMPLATE_BASEDIR", TemplateBaseDir: "GTS_TEMPLATE_BASEDIR",
AccountsOpenRegistration: "GTS_ACCOUNTS_OPEN_REGISTRATION",
} }
} }

View File

@ -304,6 +304,7 @@ func (ps *postgresService) GetAccountByUserID(userID string, account *model.Acco
return err return err
} }
if err := ps.conn.Model(account).Where("id = ?", user.AccountID).Select(); err != nil { if err := ps.conn.Model(account).Where("id = ?", user.AccountID).Select(); err != nil {
fmt.Println(account)
if err == pg.ErrNoRows { if err == pg.ErrNoRows {
return ErrNoEntries{} return ErrNoEntries{}
} }
@ -394,7 +395,7 @@ func (ps *postgresService) AccountToMastoSensitive(a *model.Account) (*mastotype
fmt.Printf("fields: %+v", fields) fmt.Printf("fields: %+v", fields)
// count followers // count followers
var followers []model.Follow followers := []model.Follow{}
if err := ps.GetFollowersByAccountID(a.ID, &followers); err != nil { if err := ps.GetFollowersByAccountID(a.ID, &followers); err != nil {
if _, ok := err.(ErrNoEntries); !ok { if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting followers: %s", err) return nil, fmt.Errorf("error getting followers: %s", err)
@ -406,7 +407,7 @@ func (ps *postgresService) AccountToMastoSensitive(a *model.Account) (*mastotype
} }
// count following // count following
var following []model.Follow following := []model.Follow{}
if err := ps.GetFollowingByAccountID(a.ID, &following); err != nil { if err := ps.GetFollowingByAccountID(a.ID, &following); err != nil {
if _, ok := err.(ErrNoEntries); !ok { if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting following: %s", err) return nil, fmt.Errorf("error getting following: %s", err)
@ -418,7 +419,7 @@ func (ps *postgresService) AccountToMastoSensitive(a *model.Account) (*mastotype
} }
// count statuses // count statuses
var statuses []model.Status statuses := []model.Status{}
if err := ps.GetStatusesByAccountID(a.ID, &statuses); err != nil { if err := ps.GetStatusesByAccountID(a.ID, &statuses); err != nil {
if _, ok := err.(ErrNoEntries); !ok { if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting last statuses: %s", err) return nil, fmt.Errorf("error getting last statuses: %s", err)
@ -430,7 +431,7 @@ func (ps *postgresService) AccountToMastoSensitive(a *model.Account) (*mastotype
} }
// check when the last status was // check when the last status was
var lastStatus *model.Status lastStatus := &model.Status{}
if err := ps.GetLastStatusForAccountID(a.ID, lastStatus); err != nil { if err := ps.GetLastStatusForAccountID(a.ID, lastStatus); err != nil {
if _, ok := err.(ErrNoEntries); !ok { if _, ok := err.(ErrNoEntries); !ok {
return nil, fmt.Errorf("error getting last status: %s", err) return nil, fmt.Errorf("error getting last status: %s", err)

View File

@ -19,7 +19,6 @@
package account package account
import ( import (
"fmt"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -29,6 +28,7 @@ import (
"github.com/gotosocial/gotosocial/internal/module" "github.com/gotosocial/gotosocial/internal/module"
"github.com/gotosocial/gotosocial/internal/module/oauth" "github.com/gotosocial/gotosocial/internal/module/oauth"
"github.com/gotosocial/gotosocial/internal/router" "github.com/gotosocial/gotosocial/internal/router"
"github.com/sirupsen/logrus"
) )
const ( const (
@ -40,49 +40,62 @@ const (
type accountModule struct { type accountModule struct {
config *config.Config config *config.Config
db db.DB db db.DB
log *logrus.Logger
} }
// New returns a new account module // New returns a new account module
func New(config *config.Config, db db.DB) module.ClientAPIModule { func New(config *config.Config, db db.DB, log *logrus.Logger) module.ClientAPIModule {
return &accountModule{ return &accountModule{
config: config, config: config,
db: db, db: db,
log: log,
} }
} }
// Route attaches all routes from this module to the given router // Route attaches all routes from this module to the given router
func (m *accountModule) Route(r router.Router) error { func (m *accountModule) Route(r router.Router) error {
r.AttachHandler(http.MethodPost, basePath, m.AccountCreatePOSTHandler)
r.AttachHandler(http.MethodGet, verifyPath, m.AccountVerifyGETHandler) r.AttachHandler(http.MethodGet, verifyPath, m.AccountVerifyGETHandler)
return nil return nil
} }
func (m *accountModule) AccountCreatePOSTHandler(c *gin.Context) {
l := m.log.WithField("func", "AccountCreatePOSTHandler")
l.Trace("checking if registration is open")
if !m.config.AccountsConfig.OpenRegistration {
l.Trace("account registration is closed, returning error to client")
}
}
// AccountVerifyGETHandler serves a user's account details to them IF they reached this // AccountVerifyGETHandler serves a user's account details to them IF they reached this
// handler while in possession of a valid token, according to the oauth middleware. // handler while in possession of a valid token, according to the oauth middleware.
func (m *accountModule) AccountVerifyGETHandler(c *gin.Context) { func (m *accountModule) AccountVerifyGETHandler(c *gin.Context) {
i, ok := c.Get(oauth.SessionAuthorizedUser) l := m.log.WithField("func", "AccountVerifyGETHandler")
fmt.Println(i)
l.Trace("getting account details from session")
i, ok := c.Get(oauth.SessionAuthorizedAccount)
if !ok { if !ok {
l.Trace("no account in session, returning error to client")
c.JSON(http.StatusUnauthorized, gin.H{"error": "The access token is invalid"}) c.JSON(http.StatusUnauthorized, gin.H{"error": "The access token is invalid"})
return return
} }
userID, ok := (i).(string) l.Trace("attempting to convert account interface into account struct...")
if !ok || userID == "" { acct, ok := i.(*model.Account)
if !ok {
l.Tracef("could not convert %+v into account struct, returning error to client", i)
c.JSON(http.StatusUnauthorized, gin.H{"error": "The access token is invalid"}) c.JSON(http.StatusUnauthorized, gin.H{"error": "The access token is invalid"})
return return
} }
acct := &model.Account{} l.Tracef("retrieved account %+v, converting to mastosensitive...", acct)
if err := m.db.GetAccountByUserID(userID, acct); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
acctSensitive, err := m.db.AccountToMastoSensitive(acct) acctSensitive, err := m.db.AccountToMastoSensitive(acct)
if err != nil { if err != nil {
l.Tracef("could not convert account into mastosensitive account: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
l.Tracef("conversion successful, returning OK and mastosensitive account %+v", acctSensitive)
c.JSON(http.StatusOK, acctSensitive) c.JSON(http.StatusOK, acctSensitive)
} }

View File

@ -216,10 +216,19 @@ func (suite *AccountTestSuite) TestAPIInitialize() {
} }
r.AttachMiddleware(func(c *gin.Context) { r.AttachMiddleware(func(c *gin.Context) {
account := &model.Account{}
if err := suite.db.GetAccountByUserID(suite.testUser.ID, account); err != nil || account == nil {
suite.T().Log(err)
suite.FailNowf("no account found for user %s, continuing with unauthenticated request: %+v", "", suite.testUser.ID, account)
fmt.Println(account)
return
}
c.Set(oauth.SessionAuthorizedAccount, account)
c.Set(oauth.SessionAuthorizedUser, suite.testUser.ID) c.Set(oauth.SessionAuthorizedUser, suite.testUser.ID)
}) })
acct := New(suite.config, suite.db) acct := New(suite.config, suite.db, log)
acct.Route(r) acct.Route(r)
r.Start() r.Start()

View File

@ -47,11 +47,12 @@ import (
) )
const ( const (
appsPath = "/api/v1/apps" appsPath = "/api/v1/apps"
authSignInPath = "/auth/sign_in" authSignInPath = "/auth/sign_in"
oauthTokenPath = "/oauth/token" oauthTokenPath = "/oauth/token"
oauthAuthorizePath = "/oauth/authorize" oauthAuthorizePath = "/oauth/authorize"
SessionAuthorizedUser = "authorized_user" SessionAuthorizedUser = "authorized_user"
SessionAuthorizedAccount = "authorized_account"
) )
// oauthModule is an oauth2 oauthModule that satisfies the ClientAPIModule interface // oauthModule is an oauth2 oauthModule that satisfies the ClientAPIModule interface
@ -406,16 +407,30 @@ func (m *oauthModule) authorizePOSTHandler(c *gin.Context) {
MIDDLEWARE MIDDLEWARE
*/ */
// oauthTokenMiddleware // 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 *oauthModule) oauthTokenMiddleware(c *gin.Context) { func (m *oauthModule) oauthTokenMiddleware(c *gin.Context) {
l := m.log.WithField("func", "ValidatePassword") l := m.log.WithField("func", "ValidatePassword")
l.Trace("entering OauthTokenMiddleware") l.Trace("entering OauthTokenMiddleware")
if ti, err := m.oauthServer.ValidationBearerToken(c.Request); err == nil {
l.Tracef("authenticated user %s with bearer token, scope is %s", ti.GetUserID(), ti.GetScope()) ti, err := m.oauthServer.ValidationBearerToken(c.Request)
c.Set(SessionAuthorizedUser, ti.GetUserID()) if err != nil {
} else { l.Trace("no valid token presented: continuing with unauthenticated request")
l.Trace("continuing with unauthenticated request") return
} }
l.Tracef("authenticated user %s with bearer token, scope is %s", ti.GetUserID(), ti.GetScope())
acct := &model.Account{}
if err := m.db.GetAccountByUserID(ti.GetUserID(), acct); err != nil || acct == nil {
l.Tracef("no account found for user %s, continuing with unauthenticated request", ti.GetUserID())
return
}
c.Set(SessionAuthorizedAccount, acct)
c.Set(SessionAuthorizedUser, ti.GetUserID())
} }
/* /*

View File

@ -0,0 +1,23 @@
/*
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 util
func CheckPasswordStrength(password string) bool {
return true
}

View File

@ -67,3 +67,21 @@ type Account struct {
// An extra entity to be used with API methods to verify credentials and update credentials. // An extra entity to be used with API methods to verify credentials and update credentials.
Source *Source `json:"source"` Source *Source `json:"source"`
} }
// AccountCreateRequest represents the form submitted during a POST request to /api/v1/accounts.
// See https://docs.joinmastodon.org/methods/accounts/
type AccountCreateRequest struct {
// Text that will be reviewed by moderators if registrations require manual approval.
Reason string `form:"reason"`
// The desired username for the account
Username string `form:"username"`
// The email address to be used for login
Email string `form:"email"`
// The password to be used for login
Password string `form:"password"`
// Whether the user agrees to the local rules, terms, and policies.
// These should be presented to the user in order to allow them to consent before setting this parameter to TRUE.
Agreement bool `form:"agreement"`
// The language of the confirmation email that will be sent
Locale string `form:"locale"`
}