diff --git a/middlewares/authenticate.go b/middlewares/authenticate.go index ade864c..4298795 100644 --- a/middlewares/authenticate.go +++ b/middlewares/authenticate.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/muety/wakapi/utils" + "log" "net/http" "strings" "time" @@ -94,18 +95,33 @@ func (m *AuthenticateMiddleware) tryGetUserByCookie(r *http.Request) (*models.Us return nil, err } - var user *models.User cachedUser, ok := m.cache.Get(login.Username) - if !ok { - user, err = m.userSrvc.GetUserById(login.Username) - if err != nil { - return nil, err - } - if !utils.CheckPassword(user, login.Password, m.config.PasswordSalt) { - return nil, errors.New("invalid password") - } - } else { - user = cachedUser.(*models.User) + + if ok { + return cachedUser.(*models.User), nil } + + user, err := m.userSrvc.GetUserById(login.Username) + if err != nil { + return nil, err + } + + if !CheckAndMigratePassword(user, login, m.config.PasswordSalt, m.userSrvc) { + return nil, errors.New("invalid password") + } + return user, nil } + +// migrate old md5-hashed passwords to new salted bcrypt hashes for backwards compatibility +func CheckAndMigratePassword(user *models.User, login *models.Login, salt string, userServiceRef *services.UserService) bool { + if utils.IsMd5(user.Password) { + if utils.CheckPasswordMd5(user, login.Password) { + log.Printf("migrating old md5 password to new bcrypt format for user '%s'", user.ID) + userServiceRef.MigrateMd5Password(user, login) + return true + } + return false + } + return utils.CheckPasswordBcrypt(user, login.Password, salt) +} diff --git a/routes/public.go b/routes/public.go index f4b5fc7..9c7a5ce 100644 --- a/routes/public.go +++ b/routes/public.go @@ -3,6 +3,7 @@ package routes import ( "fmt" "github.com/gorilla/schema" + "github.com/muety/wakapi/middlewares" "github.com/muety/wakapi/models" "github.com/muety/wakapi/services" "github.com/muety/wakapi/utils" @@ -76,7 +77,8 @@ func (h *IndexHandler) Login(w http.ResponseWriter, r *http.Request) { return } - if !utils.CheckPassword(user, login.Password, h.config.PasswordSalt) { + // TODO: depending on middleware package here is a hack + if !middlewares.CheckAndMigratePassword(user, &login, h.config.PasswordSalt, h.userSrvc) { respondAlert(w, "invalid credentials", "", "", http.StatusUnauthorized) return } diff --git a/services/user.go b/services/user.go index 749340c..81768a8 100644 --- a/services/user.go +++ b/services/user.go @@ -1,6 +1,7 @@ package services import ( + "errors" "github.com/jinzhu/gorm" "github.com/muety/wakapi/models" "github.com/muety/wakapi/utils" @@ -67,3 +68,19 @@ func (srv *UserService) CreateOrGet(signup *models.Signup) (*models.User, bool, return u, false, nil } + +func (srv *UserService) MigrateMd5Password(user *models.User, login *models.Login) (*models.User, error) { + user.Password = login.Password + if err := utils.HashPassword(user, srv.Config.PasswordSalt); err != nil { + return nil, err + } + + result := srv.Db.Model(user).Update("password", user.Password) + if err := result.Error; err != nil { + return nil, err + } else if result.RowsAffected < 1 { + return nil, errors.New("nothing changes") + } + + return user, nil +} diff --git a/utils/auth.go b/utils/auth.go index d47aaea..0721bbf 100644 --- a/utils/auth.go +++ b/utils/auth.go @@ -1,7 +1,9 @@ package utils import ( + "crypto/md5" "encoding/base64" + "encoding/hex" "errors" "github.com/muety/wakapi/models" "golang.org/x/crypto/bcrypt" @@ -10,6 +12,8 @@ import ( "strings" ) +var md5Regex = regexp.MustCompile(`^[a-f0-9]{32}$`) + func ExtractBasicAuth(r *http.Request) (username, password string, err error) { authHeader := strings.Split(r.Header.Get("Authorization"), " ") if len(authHeader) != 2 || authHeader[0] != "Basic" { @@ -54,11 +58,25 @@ func ExtractCookieAuth(r *http.Request, config *models.Config) (login *models.Lo return login, nil } -func CheckPassword(user *models.User, password, salt string) bool { +func IsMd5(hash string) bool { + return md5Regex.Match([]byte(hash)) +} + +func CheckPasswordBcrypt(user *models.User, password, salt string) bool { err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password+salt)) return err == nil } +// deprecated, only here for backwards compatibility +func CheckPasswordMd5(user *models.User, password string) bool { + hash := md5.Sum([]byte(password)) + hashStr := hex.EncodeToString(hash[:]) + if hashStr == user.Password { + return true + } + return false +} + // inplace func HashPassword(u *models.User, salt string) error { bytes, err := bcrypt.GenerateFromPassword([]byte(u.Password+salt), bcrypt.DefaultCost) diff --git a/version.txt b/version.txt index 3e1ad72..8e03717 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.5.0 \ No newline at end of file +1.5.1 \ No newline at end of file