From cda4127551e1515b299799eaec7c1e5fcc88a44a Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Fri, 23 Feb 2018 19:14:14 +0100 Subject: [PATCH] Make password strength configurable --- api/user.go | 13 +++++++------ api/user_test.go | 8 ++++---- app.go | 4 ++-- auth/authentication_test.go | 4 ++-- auth/password.go | 4 +--- auth/password_test.go | 7 +++---- config/config.go | 1 + database/database.go | 4 ++-- database/database_test.go | 4 ++-- router/router.go | 7 ++++--- router/router_test.go | 5 +++-- 11 files changed, 31 insertions(+), 30 deletions(-) diff --git a/api/user.go b/api/user.go index 7155d8c..e09fab8 100644 --- a/api/user.go +++ b/api/user.go @@ -21,7 +21,8 @@ type UserDatabase interface { // The UserAPI provides handlers for managing users. type UserAPI struct { - DB UserDatabase + DB UserDatabase + PasswordStrength int } // GetUsers returns all the users @@ -49,7 +50,7 @@ func (a *UserAPI) CreateUser(ctx *gin.Context) { if len(user.Pass) == 0 { ctx.AbortWithError(400, errors.New("password may not be empty")) } else { - internal := toInternal(&user, []byte{}) + internal := a.toInternal(&user, []byte{}) if a.DB.GetUserByName(internal.Name) == nil { a.DB.CreateUser(internal) ctx.JSON(200, toExternal(internal)) @@ -95,7 +96,7 @@ func (a *UserAPI) ChangePassword(ctx *gin.Context) { pw := userPassword{} if err := ctx.Bind(&pw); err == nil { user := a.DB.GetUserByID(auth.GetUserID(ctx)) - user.Pass = auth.CreatePassword(pw.Pass) + user.Pass = auth.CreatePassword(pw.Pass, a.PasswordStrength) a.DB.UpdateUser(user) } } @@ -106,7 +107,7 @@ func (a *UserAPI) UpdateUserByID(ctx *gin.Context) { var user *model.UserExternal if err := ctx.Bind(&user); err == nil { if oldUser := a.DB.GetUserByID(id); oldUser != nil { - internal := toInternal(user, oldUser.Pass) + internal := a.toInternal(user, oldUser.Pass) internal.ID = id a.DB.UpdateUser(internal) ctx.JSON(200, toExternal(internal)) @@ -124,13 +125,13 @@ func toUInt(id string) (uint, error) { return uint(parsed), err } -func toInternal(response *model.UserExternal, pw []byte) *model.User { +func (a *UserAPI) toInternal(response *model.UserExternal, pw []byte) *model.User { user := &model.User{ Name: response.Name, Admin: response.Admin, } if response.Pass != "" { - user.Pass = auth.CreatePassword(response.Pass) + user.Pass = auth.CreatePassword(response.Pass, a.PasswordStrength) } else { user.Pass = pw } diff --git a/api/user_test.go b/api/user_test.go index eeff6bd..f358c9a 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -119,7 +119,7 @@ func (s *UserSuite) Test_DeleteUserByID() { func (s *UserSuite) Test_CreateUser() { pwByte := []byte{1, 2, 3} - patch := monkey.Patch(auth.CreatePassword, func(pw string) []byte { + patch := monkey.Patch(auth.CreatePassword, func(pw string, strength int) []byte { if pw == "mylittlepony" { return pwByte } @@ -160,7 +160,7 @@ func (s *UserSuite) Test_CreateUser_NoName() { func (s *UserSuite) Test_CreateUser_NameAlreadyExists() { pwByte := []byte{1, 2, 3} - monkey.Patch(auth.CreatePassword, func(pw string) []byte { return pwByte }) + monkey.Patch(auth.CreatePassword, func(pw string, strength int) []byte { return pwByte }) s.db.On("GetUserByName", "tom").Return(&model.User{ID: 3, Name: "tom"}) @@ -215,7 +215,7 @@ func (s *UserSuite) Test_UpdateUserByID_UpdateNotPassword() { func (s *UserSuite) Test_UpdateUserByID_UpdatePassword() { pwByte := []byte{1, 2, 3} - patch := monkey.Patch(auth.CreatePassword, func(pw string) []byte { return pwByte }) + patch := monkey.Patch(auth.CreatePassword, func(pw string, strength int) []byte { return pwByte }) defer patch.Unpatch() s.db.On("GetUserByID", uint(2)).Return(normalUser) @@ -236,7 +236,7 @@ func (s *UserSuite) Test_UpdateUserByID_UpdatePassword() { func (s *UserSuite) Test_UpdatePassword() { pwByte := []byte{1, 2, 3} - createPasswordPatch := monkey.Patch(auth.CreatePassword, func(pw string) []byte { return pwByte }) + createPasswordPatch := monkey.Patch(auth.CreatePassword, func(pw string, strength int) []byte { return pwByte }) defer createPasswordPatch.Unpatch() patchUser := monkey.Patch(auth.GetUserID, func(*gin.Context) uint { return 1 }) defer patchUser.Unpatch() diff --git a/app.go b/app.go index 25f68ea..bb23c07 100644 --- a/app.go +++ b/app.go @@ -31,14 +31,14 @@ func main() { fmt.Println("Starting Gotify version", vInfo.Version+"@"+BuildDate) rand.Seed(time.Now().UnixNano()) conf := config.Get() - db, err := database.New(conf.Database.Dialect, conf.Database.Connection, conf.DefaultUser.Name, conf.DefaultUser.Pass) + db, err := database.New(conf.Database.Dialect, conf.Database.Connection, conf.DefaultUser.Name, conf.DefaultUser.Pass, conf.PassStrength) if err != nil { panic(err) } defer db.Close() gin.SetMode(gin.ReleaseMode) - engine, closeable := router.Create(db, vInfo) + engine, closeable := router.Create(db, vInfo, conf) defer closeable() runner.Run(engine, conf) diff --git a/auth/authentication_test.go b/auth/authentication_test.go index f2ce5e2..4b63baa 100644 --- a/auth/authentication_test.go +++ b/auth/authentication_test.go @@ -39,8 +39,8 @@ func (s *AuthenticationSuite) SetupSuite() { s.DB.On("GetUserByID", uint(1)).Return(&model.User{ID: 1, Name: "irrelevant", Admin: false}) s.DB.On("GetUserByID", uint(2)).Return(&model.User{ID: 2, Name: "irrelevant", Admin: true}) - s.DB.On("GetUserByName", "existing").Return(&model.User{Name: "existing", Pass: CreatePassword("pw")}) - s.DB.On("GetUserByName", "admin").Return(&model.User{Name: "admin", Pass: CreatePassword("pw"), Admin: true}) + s.DB.On("GetUserByName", "existing").Return(&model.User{Name: "existing", Pass: CreatePassword("pw", 5)}) + s.DB.On("GetUserByName", "admin").Return(&model.User{Name: "admin", Pass: CreatePassword("pw", 5), Admin: true}) s.DB.On("GetUserByName", mock.Anything).Return(nil) } diff --git a/auth/password.go b/auth/password.go index 9e69420..a475ee6 100644 --- a/auth/password.go +++ b/auth/password.go @@ -2,10 +2,8 @@ package auth import "golang.org/x/crypto/bcrypt" -var strength = 13 - // CreatePassword returns a hashed version of the given password. -func CreatePassword(pw string) []byte { +func CreatePassword(pw string, strength int) []byte { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(pw), strength) if err != nil { panic(err) diff --git a/auth/password_test.go b/auth/password_test.go index fd254fb..1bb3787 100644 --- a/auth/password_test.go +++ b/auth/password_test.go @@ -9,16 +9,15 @@ import ( ) func TestPasswordSuccess(t *testing.T) { - password := CreatePassword("secret") + password := CreatePassword("secret", 5) assert.Equal(t, true, ComparePassword(password, []byte("secret"))) } func TestPasswordFailure(t *testing.T) { - password := CreatePassword("secret") + password := CreatePassword("secret", 5) assert.Equal(t, false, ComparePassword(password, []byte("secretx"))) } func TestBCryptFailure(t *testing.T) { - strength = 12312 // invalid value - assert.Panics(t, func() { CreatePassword("secret") }) + assert.Panics(t, func() { CreatePassword("secret", 12312) }) } diff --git a/config/config.go b/config/config.go index 179f8cb..83bc3d7 100644 --- a/config/config.go +++ b/config/config.go @@ -28,6 +28,7 @@ type Configuration struct { Name string `default:"admin"` Pass string `default:"admin"` } + PassStrength int `default:"10"` } // Get returns the configuration extracted from env variables or config file. diff --git a/database/database.go b/database/database.go index 3ef3d03..9370960 100644 --- a/database/database.go +++ b/database/database.go @@ -10,7 +10,7 @@ import ( ) // New creates a new wrapper for the gorm database framework. -func New(dialect, connection, defaultUser, defaultPass string) (*GormDatabase, error) { +func New(dialect, connection, defaultUser, defaultPass string, strength int) (*GormDatabase, error) { db, err := gorm.Open(dialect, connection) if err != nil { return nil, err @@ -25,7 +25,7 @@ func New(dialect, connection, defaultUser, defaultPass string) (*GormDatabase, e if !db.HasTable(new(model.User)) && !db.HasTable(new(model.Message)) && !db.HasTable(new(model.Client)) && !db.HasTable(new(model.Application)) { db.AutoMigrate(new(model.User), new(model.Application), new(model.Message), new(model.Client)) - db.Create(&model.User{Name: defaultUser, Pass: auth.CreatePassword(defaultPass), Admin: true}) + db.Create(&model.User{Name: defaultUser, Pass: auth.CreatePassword(defaultPass, strength), Admin: true}) } return &GormDatabase{DB: db}, nil diff --git a/database/database_test.go b/database/database_test.go index bd1abbb..d1772ca 100644 --- a/database/database_test.go +++ b/database/database_test.go @@ -18,7 +18,7 @@ type DatabaseSuite struct { } func (s *DatabaseSuite) BeforeTest(suiteName, testName string) { - db, err := New("sqlite3", "testdb.db", "defaultUser", "defaultPass") + db, err := New("sqlite3", "testdb.db", "defaultUser", "defaultPass", 5) assert.Nil(s.T(), err) s.db = db } @@ -29,6 +29,6 @@ func (s *DatabaseSuite) AfterTest(suiteName, testName string) { } func TestInvalidDialect(t *testing.T) { - _, err := New("asdf", "testdb.db", "defaultUser", "defaultPass") + _, err := New("asdf", "testdb.db", "defaultUser", "defaultPass", 5) assert.NotNil(t, err) } diff --git a/router/router.go b/router/router.go index b8391c8..4bc395e 100644 --- a/router/router.go +++ b/router/router.go @@ -12,18 +12,19 @@ import ( "net/http" + "github.com/gotify/server/config" "github.com/gotify/server/docs" - "github.com/gotify/server/stream" "github.com/gotify/server/model" + "github.com/gotify/server/stream" ) // Create creates the gin engine with all routes. -func Create(db *database.GormDatabase, vInfo *model.VersionInfo) (*gin.Engine, func()) { +func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Configuration) (*gin.Engine, func()) { streamHandler := stream.New(200*time.Second, 15*time.Second) authentication := auth.Auth{DB: db} messageHandler := api.MessageAPI{Notifier: streamHandler, DB: db} tokenHandler := api.TokenAPI{DB: db} - userHandler := api.UserAPI{DB: db} + userHandler := api.UserAPI{DB: db, PasswordStrength: conf.PassStrength} g := gin.New() diff --git a/router/router_test.go b/router/router_test.go index 6073a20..b704bb3 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -15,6 +15,7 @@ import ( "github.com/gotify/server/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "github.com/gotify/server/config" ) var ( @@ -36,9 +37,9 @@ type IntegrationSuite struct { func (s *IntegrationSuite) BeforeTest(string, string) { gin.SetMode(gin.TestMode) var err error - s.db, err = database.New("sqlite3", "itest.db", "admin", "pw") + s.db, err = database.New("sqlite3", "itest.db", "admin", "pw", 5) assert.Nil(s.T(), err) - g, closable := Create(s.db, &model.VersionInfo{Version:"1.0.0", BuildDate:"2018-02-20-17:30:47", Branch:"master", Commit:"asdasds"}) + g, closable := Create(s.db, &model.VersionInfo{Version:"1.0.0", BuildDate:"2018-02-20-17:30:47", Branch:"master", Commit:"asdasds"}, &config.Configuration{PassStrength:5}) s.closable = closable s.server = httptest.NewServer(g) }