Go refactor: merge similar functions (#16098)

### What problem does this PR solve?

Merge password related functions

### Type of change

- [x] Refactoring

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
This commit is contained in:
Jin Hai
2026-06-16 19:26:42 +08:00
committed by GitHub
parent 59aedad5e1
commit 3d8bc76e27
3 changed files with 21 additions and 147 deletions

View File

@@ -250,12 +250,12 @@ func (s *Service) CreateUser(username, password, role string) (map[string]interf
return nil, fmt.Errorf("User '%s' already exists", username)
}
decryptedPassword, err := DecryptPassword(password)
decryptedPassword, err := common.DecryptPassword(password)
if err != nil {
return nil, fmt.Errorf("failed to decrypt password: %w", err)
}
hashedPassword, err := GenerateWerkzeugPasswordHash(decryptedPassword, 150000)
hashedPassword, err := common.GenerateWerkzeugPasswordHash(decryptedPassword)
if err != nil {
return nil, fmt.Errorf("failed to hash password: %w", err)
}
@@ -770,16 +770,16 @@ func (s *Service) ChangePassword(username, newPassword string) error {
user := userList[0]
decryptedPassword, err := DecryptPassword(newPassword)
decryptedPassword, err := common.DecryptPassword(newPassword)
if err != nil {
return fmt.Errorf("failed to decrypt password: %w", err)
}
if user.Password != nil && CheckWerkzeugPassword(decryptedPassword, *user.Password) {
if user.Password != nil && common.CheckWerkzeugPassword(decryptedPassword, *user.Password) {
return nil
}
hashedPassword, err := GenerateWerkzeugPasswordHash(decryptedPassword, 150000)
hashedPassword, err := common.GenerateWerkzeugPasswordHash(decryptedPassword)
if err != nil {
return fmt.Errorf("failed to hash password: %w", err)
}
@@ -1784,7 +1784,7 @@ func (s *Service) InitDefaultAdmin() error {
// Python: password = encode_to_base64(password) = base64.b64encode(password)
// Then: generate_password_hash(base64_password) creates werkzeug hash
password := base64.StdEncoding.EncodeToString([]byte(defaultPassword))
hashedPassword, err := GenerateWerkzeugPasswordHash(password, 150000)
hashedPassword, err := common.GenerateWerkzeugPasswordHash(password)
if err != nil {
return fmt.Errorf("failed to hash password: %w", err)
}

View File

@@ -14,7 +14,7 @@
// limitations under the License.
//
package admin
package common
import (
"crypto/rand"
@@ -148,7 +148,7 @@ func IsWerkzeugHash(hashStr string) bool {
// GenerateWerkzeugPasswordHash generates a werkzeug-compatible password hash using scrypt
// This matches Python werkzeug's default behavior
func GenerateWerkzeugPasswordHash(password string, iterations int) (string, error) {
func GenerateWerkzeugPasswordHash(password string) (string, error) {
// Generate random bytes (12 bytes will produce 16-char base64 string)
randomBytes := make([]byte, 12)
if _, err := rand.Read(randomBytes); err != nil {
@@ -181,7 +181,7 @@ func DecryptPassword(encryptedPassword string) (string, error) {
}
// Load private key
privateKey, err := loadPrivateKey()
privateKey, err := LoadPrivateKey()
if err != nil {
return "", err
}
@@ -196,8 +196,8 @@ func DecryptPassword(encryptedPassword string) (string, error) {
return string(plaintext), nil
}
// loadPrivateKey loads and decrypts the RSA private key from conf/private.pem
func loadPrivateKey() (*rsa.PrivateKey, error) {
// LoadPrivateKey loads and decrypts the RSA private key from conf/private.pem
func LoadPrivateKey() (*rsa.PrivateKey, error) {
// Read private key file
keyData, err := os.ReadFile("conf/private.pem")
if err != nil {

View File

@@ -18,14 +18,11 @@ package service
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"hash"
@@ -125,13 +122,13 @@ func (s *UserService) Register(req *RegisterRequest) (*entity.User, common.Error
return nil, common.CodeServerError, fmt.Errorf("failed to check existing user: %w", err)
}
decryptedPassword, err := s.decryptPasswordForRegister(req.Password)
decryptedPassword, err := common.DecryptPassword(req.Password)
if err != nil {
return nil, common.CodeExceptionError, err
}
var hashedPassword string
hashedPassword, err = s.HashPassword(decryptedPassword)
hashedPassword, err = common.GenerateWerkzeugPasswordHash(decryptedPassword)
if err != nil {
return nil, common.CodeServerError, fmt.Errorf("failed to hash password: %w", err)
}
@@ -362,7 +359,7 @@ func (s *UserService) Login(req *LoginRequest) (*entity.User, common.ErrorCode,
}
// Decrypt password using RSA
decryptedPassword, err := s.decryptPassword(req.Password)
decryptedPassword, err := common.DecryptPassword(req.Password)
if err != nil {
return nil, common.CodeServerError, fmt.Errorf("failed to decrypt password: %w", err)
}
@@ -399,7 +396,7 @@ func (s *UserService) LoginByEmail(req *EmailLoginRequest) (*entity.User, common
return nil, common.CodeAuthenticationError, fmt.Errorf("email: %s is not registered!", req.Email)
}
decryptedPassword, err := s.decryptPassword(req.Password)
decryptedPassword, err := common.DecryptPassword(req.Password)
if err != nil {
return nil, common.CodeServerError, fmt.Errorf("fail to crypt password")
}
@@ -473,31 +470,6 @@ func (s *UserService) ListUsers(page, pageSize int) ([]*UserResponse, int64, com
return responses, total, common.CodeSuccess, nil
}
// HashPassword generate password hash using scrypt (werkzeug compatible)
// The password should already be base64 encoded (from decrypt process)
// Werkzeug default format: scrypt:32768:8:1$base64(salt)$hex(hash)
// IMPORTANT: werkzeug uses the base64-encoded salt string as UTF-8 bytes, NOT the decoded bytes
func (s *UserService) HashPassword(password string) (string, error) {
// Generate random bytes (12 bytes will produce 16-char base64 string)
randomBytes, err := s.generateSalt()
if err != nil {
return "", fmt.Errorf("failed to generate salt: %w", err)
}
// Encode to base64 string (this will be 16 characters)
saltB64 := base64.StdEncoding.EncodeToString(randomBytes)
// Use scrypt with werkzeug default parameters: N=32768, r=8, p=1, keyLen=64
// IMPORTANT: werkzeug uses the base64 string as UTF-8 bytes, NOT the decoded bytes
hash, err := scrypt.Key([]byte(password), []byte(saltB64), 32768, 8, 1, 64)
if err != nil {
return "", fmt.Errorf("failed to compute scrypt hash: %w", err)
}
// Format: scrypt:n:r:p$base64(salt)$hex(hash)
return fmt.Sprintf("scrypt:32768:8:1$%s$%x", saltB64, hash), nil
}
// VerifyPassword verify password
// Supports both werkzeug pbkdf2 format (pbkdf2:sha256:iterations$salt$hash) and scrypt format
func (s *UserService) VerifyPassword(hashedPassword, password string) bool {
@@ -618,15 +590,6 @@ func (s *UserService) verifyScryptPassword(hashedPassword, password string) bool
return s.constantTimeCompare(expectedHash, computed)
}
// generateSalt generates a random 12-byte salt (werkzeug default)
func (s *UserService) generateSalt() ([]byte, error) {
salt := make([]byte, 12)
if _, err := rand.Read(salt); err != nil {
return nil, fmt.Errorf("failed to generate random salt: %w", err)
}
return salt, nil
}
// constantTimeCompare constant time comparison
func (s *UserService) constantTimeCompare(a, b []byte) bool {
if len(a) != len(b) {
@@ -641,95 +604,6 @@ func (s *UserService) constantTimeCompare(a, b []byte) bool {
return result == 0
}
// loadPrivateKey loads and decrypts the RSA private key from conf/private.pem
// nolint:static check // DecryptPEMBlock is deprecated but still works for traditional PEM encryption
func (s *UserService) loadPrivateKey() (*rsa.PrivateKey, error) {
// Read private key file
keyData, err := os.ReadFile("conf/private.pem")
if err != nil {
return nil, fmt.Errorf("failed to read private key file: %w", err)
}
// Parse PEM block
block, _ := pem.Decode(keyData)
if block == nil {
return nil, errors.New("failed to decode PEM block")
}
// Decrypt the PEM block if it's encrypted
var privateKey interface{}
if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
// Decrypt using password "Welcome"
// Note: DecryptPEMBlock is deprecated but still functional for traditional PEM encryption
decryptedData, err := x509.DecryptPEMBlock(block, []byte("Welcome"))
if err != nil {
return nil, fmt.Errorf("failed to decrypt private key: %w", err)
}
// Parse the decrypted key
privateKey, err = x509.ParsePKCS1PrivateKey(decryptedData)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %w", err)
}
} else {
// Not encrypted, parse directly
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %w", err)
}
}
rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("not an RSA private key")
}
return rsaPrivateKey, nil
}
// decryptPassword decrypts the password using RSA private key
func (s *UserService) decryptPassword(encryptedPassword string) (string, error) {
// Try to decode base64
ciphertext, err := base64.StdEncoding.DecodeString(encryptedPassword)
if err != nil {
// If base64 decoding fails, assume it's already a plain password
return encryptedPassword, nil
}
// Load private key
privateKey, err := s.loadPrivateKey()
if err != nil {
return "", err
}
// Decrypt using PKCS#1 v1.5
plaintext, err := rsa.DecryptPKCS1v15(nil, privateKey, ciphertext)
if err != nil {
// If decryption fails, assume it's already a plain password
return encryptedPassword, nil
}
return string(plaintext), nil
}
func (s *UserService) decryptPasswordForRegister(encryptedPassword string) (string, error) {
ciphertext, err := base64.StdEncoding.DecodeString(encryptedPassword)
if err != nil {
return "", fmt.Errorf("Error('Incorrect padding')")
}
privateKey, err := s.loadPrivateKey()
if err != nil {
return "", err
}
plaintext, err := rsa.DecryptPKCS1v15(nil, privateKey, ciphertext)
if err != nil {
return "Fail to decrypt password!", nil
}
return string(plaintext), nil
}
func defaultUserLanguage() string {
if strings.Contains(os.Getenv("LANG"), "zh_CN") {
return "Chinese"
@@ -898,7 +772,7 @@ func (s *UserService) UpdateUserSettings(user *entity.User, req *UpdateSettingsR
if err != nil {
return common.CodeExceptionError, fmt.Errorf("Error('Incorrect padding')")
}
privateKey, err := s.loadPrivateKey()
privateKey, err := common.LoadPrivateKey()
if err != nil {
return common.CodeExceptionError, err
}
@@ -921,7 +795,7 @@ func (s *UserService) UpdateUserSettings(user *entity.User, req *UpdateSettingsR
return common.CodeExceptionError, err
}
hashedPassword, err := s.HashPassword(string(newPasswordBytes))
hashedPassword, err := common.GenerateWerkzeugPasswordHash(string(newPasswordBytes))
if err != nil {
return common.CodeExceptionError, err
}
@@ -967,7 +841,7 @@ func (s *UserService) ChangePassword(user *entity.User, req *ChangePasswordReque
// If new password is provided, update password
if req.NewPassword != nil {
hashedPassword, err := s.HashPassword(*req.NewPassword)
hashedPassword, err := common.GenerateWerkzeugPasswordHash(*req.NewPassword)
if err != nil {
return common.CodeServerError, fmt.Errorf("failed to hash new password: %w", err)
}
@@ -1411,11 +1285,11 @@ func (s *UserService) ForgotResetPassword(req *ForgotResetPasswordRequest) (*ent
return nil, common.CodeAuthenticationError, fmt.Errorf("email not verified")
}
plain, err := s.decryptPassword(req.NewPassword)
plain, err := common.DecryptPassword(req.NewPassword)
if err != nil {
return nil, common.CodeServerError, fmt.Errorf("fail to decrypt password")
}
confirm, err := s.decryptPassword(req.ConfirmNewPassword)
confirm, err := common.DecryptPassword(req.ConfirmNewPassword)
if err != nil {
return nil, common.CodeServerError, fmt.Errorf("fail to decrypt password")
}
@@ -1428,7 +1302,7 @@ func (s *UserService) ForgotResetPassword(req *ForgotResetPasswordRequest) (*ent
return nil, common.CodeDataError, fmt.Errorf("invalid email")
}
hashed, err := s.HashPassword(plain)
hashed, err := common.GenerateWerkzeugPasswordHash(plain)
if err != nil {
return nil, common.CodeServerError, fmt.Errorf("failed to hash new password: %w", err)
}