From 3d8bc76e2760c02e87fb4396dfe9f12b6f283890 Mon Sep 17 00:00:00 2001 From: Jin Hai Date: Tue, 16 Jun 2026 19:26:42 +0800 Subject: [PATCH] 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 --- internal/admin/service.go | 12 +- internal/{admin => common}/password.go | 10 +- internal/service/user.go | 146 ++----------------------- 3 files changed, 21 insertions(+), 147 deletions(-) rename internal/{admin => common}/password.go (96%) diff --git a/internal/admin/service.go b/internal/admin/service.go index 81a4106fc..351abafc3 100644 --- a/internal/admin/service.go +++ b/internal/admin/service.go @@ -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) } diff --git a/internal/admin/password.go b/internal/common/password.go similarity index 96% rename from internal/admin/password.go rename to internal/common/password.go index fdd0c2b36..a9276c510 100644 --- a/internal/admin/password.go +++ b/internal/common/password.go @@ -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 { diff --git a/internal/service/user.go b/internal/service/user.go index bc568c8f7..03615e3f5 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -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) }