mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
feat: add auto-migrations for old md5 password to maintain backwards compatibility
This commit is contained in:
parent
08675bd99f
commit
6c2f0cb1ec
@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/muety/wakapi/utils"
|
"github.com/muety/wakapi/utils"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -94,18 +95,33 @@ func (m *AuthenticateMiddleware) tryGetUserByCookie(r *http.Request) (*models.Us
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var user *models.User
|
|
||||||
cachedUser, ok := m.cache.Get(login.Username)
|
cachedUser, ok := m.cache.Get(login.Username)
|
||||||
if !ok {
|
|
||||||
user, err = m.userSrvc.GetUserById(login.Username)
|
if ok {
|
||||||
if err != nil {
|
return cachedUser.(*models.User), nil
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !utils.CheckPassword(user, login.Password, m.config.PasswordSalt) {
|
|
||||||
return nil, errors.New("invalid password")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
user = cachedUser.(*models.User)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
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)
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@ package routes
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
|
"github.com/muety/wakapi/middlewares"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
"github.com/muety/wakapi/services"
|
"github.com/muety/wakapi/services"
|
||||||
"github.com/muety/wakapi/utils"
|
"github.com/muety/wakapi/utils"
|
||||||
@ -76,7 +77,8 @@ func (h *IndexHandler) Login(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
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)
|
respondAlert(w, "invalid credentials", "", "", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
"github.com/muety/wakapi/utils"
|
"github.com/muety/wakapi/utils"
|
||||||
@ -67,3 +68,19 @@ func (srv *UserService) CreateOrGet(signup *models.Signup) (*models.User, bool,
|
|||||||
|
|
||||||
return u, false, nil
|
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
|
||||||
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
@ -10,6 +12,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var md5Regex = regexp.MustCompile(`^[a-f0-9]{32}$`)
|
||||||
|
|
||||||
func ExtractBasicAuth(r *http.Request) (username, password string, err error) {
|
func ExtractBasicAuth(r *http.Request) (username, password string, err error) {
|
||||||
authHeader := strings.Split(r.Header.Get("Authorization"), " ")
|
authHeader := strings.Split(r.Header.Get("Authorization"), " ")
|
||||||
if len(authHeader) != 2 || authHeader[0] != "Basic" {
|
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
|
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))
|
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password+salt))
|
||||||
return err == nil
|
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
|
// inplace
|
||||||
func HashPassword(u *models.User, salt string) error {
|
func HashPassword(u *models.User, salt string) error {
|
||||||
bytes, err := bcrypt.GenerateFromPassword([]byte(u.Password+salt), bcrypt.DefaultCost)
|
bytes, err := bcrypt.GenerateFromPassword([]byte(u.Password+salt), bcrypt.DefaultCost)
|
||||||
|
@ -1 +1 @@
|
|||||||
1.5.0
|
1.5.1
|
Loading…
Reference in New Issue
Block a user