fix(api): relax account password validation

This commit is contained in:
david
2026-07-30 10:01:19 +08:00
parent 3f7e3f941b
commit 1d5ac6470c
12 changed files with 83 additions and 11 deletions

View File

@@ -60,7 +60,7 @@ func InitPlatformRoot(database *gorm.DB) error {
// platformRootPassword 优先读取部署环境传入的 root 初始密码。
func platformRootPassword() string {
if password := os.Getenv("HEQI_PLATFORM_ROOT_PASSWORD"); len(password) >= 12 {
if password := os.Getenv("HEQI_PLATFORM_ROOT_PASSWORD"); common.IsValidAccountPassword(password) {
return password
}
return PlatformRootPassword

View File

@@ -32,3 +32,19 @@ func TestInitPlatformAccessSeedsRootRole(t *testing.T) {
t.Fatal(err)
}
}
func TestPlatformRootPassword(t *testing.T) {
t.Run("accepts six character environment password", func(t *testing.T) {
t.Setenv("HEQI_PLATFORM_ROOT_PASSWORD", "123456")
if got := platformRootPassword(); got != "123456" {
t.Fatalf("platformRootPassword() = %q, want environment password", got)
}
})
t.Run("falls back when environment password is too short", func(t *testing.T) {
t.Setenv("HEQI_PLATFORM_ROOT_PASSWORD", "12345")
if got := platformRootPassword(); got != PlatformRootPassword {
t.Fatalf("platformRootPassword() = %q, want default password", got)
}
})
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"reflect"
"strings"
"unicode/utf8"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/infra"
@@ -16,6 +17,14 @@ import (
"gorm.io/gorm"
)
// AccountPasswordMinLength 是账号密码允许的最低字符数。
const AccountPasswordMinLength = 6
// IsValidAccountPassword 仅校验账号密码的最低字符长度。
func IsValidAccountPassword(password string) bool {
return utf8.RuneCountInString(password) >= AccountPasswordMinLength
}
// PasswordHash creates the shared password representation used by account modules.
func PasswordHash(password string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)

View File

@@ -0,0 +1,24 @@
package common
import "testing"
func TestIsValidAccountPassword(t *testing.T) {
tests := []struct {
name string
password string
want bool
}{
{name: "five characters", password: "12345", want: false},
{name: "six digits", password: "123456", want: true},
{name: "six letters without complexity", password: "abcdef", want: true},
{name: "six unicode characters", password: "密码密码密码", want: true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := IsValidAccountPassword(test.password); got != test.want {
t.Fatalf("IsValidAccountPassword(%q) = %v, want %v", test.password, got, test.want)
}
})
}
}

View File

@@ -19,7 +19,7 @@ import (
// LoginRequest 是平台总后台的账号密码登录请求。
type LoginRequest struct {
Username string `json:"username" binding:"required,max=64"`
Password string `json:"password" binding:"required,min=8,max=128"`
Password string `json:"password" binding:"required"`
}
// LoginReply 是后台登录成功后的访问凭证与账号状态。
@@ -122,8 +122,8 @@ func CurrentProfile(ctx *gin.Context) {
// ChangePasswordRequest 是已登录账号的改密请求。
type ChangePasswordRequest struct {
CurrentPassword string `json:"current_password" binding:"required,min=8,max=128"`
NewPassword string `json:"new_password" binding:"required,min=12,max=128"`
CurrentPassword string `json:"current_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required"`
}
// ChangePassword 修改当前账号密码并解除首次登录改密限制。
@@ -138,6 +138,10 @@ func ChangePassword(ctx *gin.Context) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if !common.IsValidAccountPassword(request.NewPassword) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
var account models.PlatformAccount
if err := impl.DBService.Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
infra.Response.Error(ctx, errcode.ErrRecordNotFound)

View File

@@ -14,7 +14,7 @@ import (
type accountRequest struct {
Username string `json:"username" binding:"required,max=64"`
Password string `json:"password" binding:"required,min=8,max=128"`
Password string `json:"password" binding:"required"`
DisplayName string `json:"display_name" binding:"max=64"`
RoleCode string `json:"role_code" binding:"max=64"`
DeliveryBasicIdentity string `json:"delivery_basic_identity"`
@@ -50,6 +50,10 @@ func CreateDeliveryAccount(ctx *gin.Context) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if !common.IsValidAccountPassword(request.Password) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
deliveryBasicID, err := common.ResolveIdentityID(&models.DeliveryBasic{}, request.DeliveryBasicIdentity, true)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)

View File

@@ -14,7 +14,7 @@ import (
type accountRequest struct {
Username string `json:"username" binding:"required,max=64"`
Password string `json:"password" binding:"required,min=8,max=128"`
Password string `json:"password" binding:"required"`
DisplayName string `json:"display_name" binding:"max=64"`
RoleCode string `json:"role_code" binding:"max=64"`
GasBasicIdentity string `json:"gas_basic_identity"`
@@ -49,6 +49,10 @@ func CreateGasAccount(ctx *gin.Context) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if !common.IsValidAccountPassword(request.Password) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
gasBasicID, err := common.ResolveIdentityID(&models.GasBasic{}, request.GasBasicIdentity, true)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)

View File

@@ -36,7 +36,7 @@ func ListPlatformAccount(ctx *gin.Context) {
type platformAccountRequest struct {
Username string `json:"username" binding:"required,max=64"`
Password string `json:"password" binding:"required,min=8,max=128"`
Password string `json:"password" binding:"required"`
DisplayName string `json:"display_name" binding:"max=64"`
Avatar string `json:"avatar" binding:"max=512"`
PlatformRoleCode string `json:"platform_role_code" binding:"required,max=64"`
@@ -70,6 +70,10 @@ func CreatePlatformAccount(ctx *gin.Context) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if !common.IsValidAccountPassword(request.Password) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if !isAssignablePlatformRole(request.PlatformRoleCode) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return

View File

@@ -51,7 +51,7 @@ func GetStaff(ctx *gin.Context) { common.GetByIdentity[models.StaffAccount](ctx)
func CreateStaff(ctx *gin.Context) {
var request struct {
Username string `json:"username" binding:"required,max=64"`
Password string `json:"password" binding:"required,min=8,max=128"`
Password string `json:"password" binding:"required"`
Name string `json:"name" binding:"required,max=64"`
Phone string `json:"phone" binding:"max=32"`
Avatar string `json:"avatar" binding:"max=512"`
@@ -60,7 +60,9 @@ func CreateStaff(ctx *gin.Context) {
DeliveryBasicIdentity string `json:"delivery_basic_identity"`
WorkStatus string `json:"work_status" binding:"max=32"`
}
if err := ctx.ShouldBindJSON(&request); err != nil || !validStaffRole(request.RoleCode) {
if err := ctx.ShouldBindJSON(&request); err != nil ||
!common.IsValidAccountPassword(request.Password) ||
!validStaffRole(request.RoleCode) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}

View File

@@ -19,7 +19,7 @@ func GetUser(ctx *gin.Context) { common.GetByIdentity[models.UserAccount](ctx) }
func CreateUser(ctx *gin.Context) {
var request struct {
Username string `json:"username" binding:"required,max=64"`
Password string `json:"password" binding:"required,min=8,max=128"`
Password string `json:"password" binding:"required"`
Name string `json:"name" binding:"required,max=64"`
Phone string `json:"phone" binding:"max=32"`
Avatar string `json:"avatar" binding:"max=512"`
@@ -29,6 +29,10 @@ func CreateUser(ctx *gin.Context) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if !common.IsValidAccountPassword(request.Password) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
hash, err := common.PasswordHash(request.Password)
if err != nil {
infra.Response.Error(ctx, err)