Make password strength configurable
This commit is contained in:
parent
8cffa66531
commit
cda4127551
11
api/user.go
11
api/user.go
|
|
@ -22,6 +22,7 @@ type UserDatabase interface {
|
|||
// The UserAPI provides handlers for managing users.
|
||||
type UserAPI struct {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
4
app.go
4
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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue