fix: 登录验证已有密码并保留新密码规则 (#2)

This commit is contained in:
ila
2026-09-10 20:02:49 +08:00
parent cc17d1cce7
commit 722ae152ca
6 changed files with 70 additions and 8 deletions
+32
View File
@@ -100,6 +100,38 @@ func randomName(prefix string) string {
return prefix + hex.EncodeToString(b)
}
func TestMySQLLoginUsesStoredPasswordWithoutChangingCreationPolicy(t *testing.T) {
db := testDB(t)
// Fictional legacy credential: login verifies the stored hash independently
// of the policy applied when creating accounts or setting new passwords.
legacyPassword := strings.Repeat("z", 6)
u := admin.SysUser{Username: randomName("legacy"), Password: legacyPassword, RoleId: 1, Status: "2"}
if err := db.Create(&u).Error; err != nil {
t.Fatal("fixture creation failed")
}
r := Router(db, time.Now)
token := loginToken(t, r, u.Username, legacyPassword)
for _, password := range []string{strings.Repeat("y", 6), strings.Repeat("z", 5)} {
code, _ := callAPI(t, r, "POST", "/api/v1/login", "", map[string]string{"username": u.Username, "password": password})
if code != 401 {
t.Fatalf("incorrect stored password status %d, want 401", code)
}
}
code, _ := callAPI(t, r, "POST", "/api/v1/accounts", token, map[string]string{"username": randomName("new"), "password": legacyPassword})
if code != 400 {
t.Fatalf("short new password status %d, want 400", code)
}
learner, err := createAccount(db, randomName("reset"), fixturePassword, 2, u.UserId)
if err != nil {
t.Fatal(err)
}
code, _ = callAPI(t, r, "PATCH", fmt.Sprintf("/api/v1/accounts/%d", learner.ID), token, map[string]string{"password": legacyPassword})
if code != 400 {
t.Fatalf("short reset password status %d, want 400", code)
}
loginToken(t, r, learner.Username, fixturePassword)
}
func TestMySQLAccountIsolationAndRevocation(t *testing.T) {
db := testDB(t)
if err := Migrate(db); err != nil {
+1 -1
View File
@@ -42,7 +42,7 @@ func Router(db *gorm.DB, now func() time.Time) *gin.Engine {
respond(c, 400, nil, err)
return
}
name, err := credentials(input.Username, input.Password)
name, err := loginCredentials(input.Username, input.Password)
if err != nil {
respond(c, 400, nil, err)
return
+2 -1
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
@@ -13,7 +14,7 @@ func TestLoginRejectsEmailAndMissingPasswordBeforeDatabase(t *testing.T) {
for _, payload := range []string{
`{"username":"person@example.test","password":"example-password"}`,
`{"username":"learner","password":""}`,
`{"username":"learner","password":"short"}`,
`{"username":"learner","password":"` + strings.Repeat("x", 73) + `"}`,
} {
w := httptest.NewRecorder()
q := httptest.NewRequest(http.MethodPost, "/api/v1/login", bytes.NewBufferString(payload))
+15 -2
View File
@@ -62,12 +62,25 @@ func failure(code int, msg string) error { return &apiError{code, msg} }
var usernamePattern = regexp.MustCompile(`^[a-z][a-z0-9_.-]{2,31}$`)
func credentials(username, password string) (string, error) {
name, err := loginCredentials(username, password)
if err != nil {
return "", err
}
if len(password) < 10 {
return "", failure(400, "密码须为 10~72 字节")
}
return name, nil
}
// Login verifies an existing hash; account creation and normal password resets
// separately enforce the current minimum password length.
func loginCredentials(username, password string) (string, error) {
username = strings.ToLower(strings.TrimSpace(username))
if !usernamePattern.MatchString(username) {
return "", failure(400, "账号须为字母开头的 3~32 位字母、数字、点、下划线或连字符")
}
if len(password) < 10 || len(password) > 72 {
return "", failure(400, "密码须为 10~72 字节")
if len(password) == 0 || len(password) > 72 {
return "", failure(400, "密码不能为空且不能超过 72 字节")
}
return username, nil
}