mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
security: migrate to argon2id password hashing
fix: support super long passwords (resolve #494)
This commit is contained in:
parent
a8e2bc671d
commit
35ef323b19
File diff suppressed because it is too large
Load Diff
1
go.mod
1
go.mod
@ -40,6 +40,7 @@ require (
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
|
6
go.sum
6
go.sum
@ -7,6 +7,8 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736 h1:qZaEtLxnqY5mJ0fVKbk31NVhlgi0yrKm51Pq/I5wcz4=
|
||||
github.com/alexedwards/argon2id v0.0.0-20230305115115-4b3c3280a736/go.mod h1:mTeFRcTdnpzOlRjMoFYC/80HwVUreupyAiqPkCZQOXc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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=
|
||||
@ -178,6 +180,7 @@ go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
@ -199,6 +202,7 @@ golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
@ -224,11 +228,13 @@ golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
|
@ -99,11 +99,6 @@ func (m *UserServiceMock) SetWakatimeApiCredentials(user *models.User, s1, s2 st
|
||||
return args.Get(0).(*models.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *UserServiceMock) MigrateMd5Password(user *models.User, login *models.Login) (*models.User, error) {
|
||||
args := m.Called(user, login)
|
||||
return args.Get(0).(*models.User), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *UserServiceMock) GenerateResetToken(user *models.User) (*models.User, error) {
|
||||
args := m.Called(user)
|
||||
return args.Get(0).(*models.User), args.Error(1)
|
||||
|
@ -93,7 +93,7 @@ func (h *LoginHandler) PostLogin(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if !utils.CompareBcrypt(user.Password, login.Password, h.config.Security.PasswordSalt) {
|
||||
if !utils.ComparePassword(user.Password, login.Password, h.config.Security.PasswordSalt) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
templates[conf.LoginTemplate].Execute(w, h.buildViewModel(r, w).WithError("invalid credentials"))
|
||||
return
|
||||
@ -252,7 +252,7 @@ func (h *LoginHandler) PostSetPassword(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
user.Password = setRequest.Password
|
||||
user.ResetToken = ""
|
||||
if hash, err := utils.HashBcrypt(user.Password, h.config.Security.PasswordSalt); err != nil {
|
||||
if hash, err := utils.HashPassword(user.Password, h.config.Security.PasswordSalt); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
conf.Log().Request(r).Error("failed to set new password - %v", err)
|
||||
templates[conf.SetPasswordTemplate].Execute(w, h.buildViewModel(r, w).WithError("failed to set new password"))
|
||||
|
@ -217,7 +217,7 @@ func (h *SettingsHandler) actionChangePassword(w http.ResponseWriter, r *http.Re
|
||||
return http.StatusBadRequest, "", "missing parameters"
|
||||
}
|
||||
|
||||
if !utils.CompareBcrypt(user.Password, credentials.PasswordOld, h.config.Security.PasswordSalt) {
|
||||
if !utils.ComparePassword(user.Password, credentials.PasswordOld, h.config.Security.PasswordSalt) {
|
||||
return http.StatusUnauthorized, "", "invalid credentials"
|
||||
}
|
||||
|
||||
@ -226,7 +226,7 @@ func (h *SettingsHandler) actionChangePassword(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
user.Password = credentials.PasswordNew
|
||||
if hash, err := utils.HashBcrypt(user.Password, h.config.Security.PasswordSalt); err != nil {
|
||||
if hash, err := utils.HashPassword(user.Password, h.config.Security.PasswordSalt); err != nil {
|
||||
return http.StatusInternalServerError, "", conf.ErrInternalServerError
|
||||
} else {
|
||||
user.Password = hash
|
||||
|
@ -139,7 +139,6 @@ type IUserService interface {
|
||||
Delete(*models.User) error
|
||||
ResetApiKey(*models.User) (*models.User, error)
|
||||
SetWakatimeApiCredentials(*models.User, string, string) (*models.User, error)
|
||||
MigrateMd5Password(*models.User, *models.Login) (*models.User, error)
|
||||
GenerateResetToken(*models.User) (*models.User, error)
|
||||
FlushCache()
|
||||
FlushUserCache(string)
|
||||
|
@ -157,7 +157,7 @@ func (srv *UserService) CreateOrGet(signup *models.Signup, isAdmin bool) (*model
|
||||
IsAdmin: isAdmin,
|
||||
}
|
||||
|
||||
if hash, err := utils.HashBcrypt(u.Password, srv.config.Security.PasswordSalt); err != nil {
|
||||
if hash, err := utils.HashPassword(u.Password, srv.config.Security.PasswordSalt); err != nil {
|
||||
return nil, false, err
|
||||
} else {
|
||||
u.Password = hash
|
||||
@ -194,17 +194,6 @@ func (srv *UserService) SetWakatimeApiCredentials(user *models.User, apiKey stri
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (srv *UserService) MigrateMd5Password(user *models.User, login *models.Login) (*models.User, error) {
|
||||
srv.FlushUserCache(user.ID)
|
||||
user.Password = login.Password
|
||||
if hash, err := utils.HashBcrypt(user.Password, srv.config.Security.PasswordSalt); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
user.Password = hash
|
||||
}
|
||||
return srv.repository.UpdateField(user, "password", user.Password)
|
||||
}
|
||||
|
||||
func (srv *UserService) GenerateResetToken(user *models.User) (*models.User, error) {
|
||||
return srv.repository.UpdateField(user, "reset_token", uuid.NewV4())
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package utils
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"github.com/alexedwards/argon2id"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
@ -42,9 +43,22 @@ func ExtractBearerAuth(r *http.Request) (key string, err error) {
|
||||
return string(keyBytes), err
|
||||
}
|
||||
|
||||
func CompareBcrypt(wanted, actual, pepper string) bool {
|
||||
plainPassword := []byte(strings.TrimSpace(actual) + pepper)
|
||||
err := bcrypt.CompareHashAndPassword([]byte(wanted), plainPassword)
|
||||
// password hashing
|
||||
|
||||
func ComparePassword(hashed, plain, pepper string) bool {
|
||||
if hashed[0:10] == "$argon2id$" {
|
||||
return CompareArgon2Id(hashed, plain, pepper)
|
||||
}
|
||||
return CompareBcrypt(hashed, plain, pepper)
|
||||
}
|
||||
|
||||
func HashPassword(plain, pepper string) (string, error) {
|
||||
return HashArgon2Id(plain, pepper)
|
||||
}
|
||||
|
||||
func CompareBcrypt(hashed, plain, pepper string) bool {
|
||||
plainPepperedPassword := []byte(strings.TrimSpace(plain) + pepper)
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashed), plainPepperedPassword)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@ -56,3 +70,18 @@ func HashBcrypt(plain, pepper string) (string, error) {
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
func CompareArgon2Id(hashed, plain, pepper string) bool {
|
||||
plainPepperedPassword := strings.TrimSpace(plain) + pepper
|
||||
match, err := argon2id.ComparePasswordAndHash(plainPepperedPassword, hashed)
|
||||
return err == nil && match
|
||||
}
|
||||
|
||||
func HashArgon2Id(plain, pepper string) (string, error) {
|
||||
plainPepperedPassword := strings.TrimSpace(plain) + pepper
|
||||
hash, err := argon2id.CreateHash(plainPepperedPassword, argon2id.DefaultParams)
|
||||
if err == nil {
|
||||
return hash, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user