Some checks are pending
Security Checks / security (push) Waiting to run
统一 frontend、backend、test、test-log 和 out 目录,更新构建、部署及测试入口;纳入配置管理兼容与首次安全引导修复;排除本机配置、密钥和测试产物。
119 lines
3.8 KiB
Go
119 lines
3.8 KiB
Go
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"git.pcux.cn/xuans/Smail/config"
|
|
"git.pcux.cn/xuans/Smail/db"
|
|
"git.pcux.cn/xuans/Smail/models"
|
|
"git.pcux.cn/xuans/Smail/services/auth"
|
|
"git.pcux.cn/xuans/Smail/session"
|
|
"git.pcux.cn/xuans/Smail/utils/context"
|
|
"git.pcux.cn/xuans/Smail/utils/password"
|
|
"github.com/alexedwards/scs/v2"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestModifyPasswordRequiresCurrentPassword(t *testing.T) {
|
|
oldConfig := config.Instance
|
|
oldDB := db.Instance
|
|
oldLoginPairAttempts := loginPairAttempts
|
|
oldLoginAccountAttempts := loginAccountAttempts
|
|
oldLoginIPAttempts := loginIPAttempts
|
|
oldSession := session.Instance
|
|
loginPairAttempts = newLoginLimiter(loginFailureLimit)
|
|
loginAccountAttempts = newLoginLimiter(loginAccountFailureLimit)
|
|
loginIPAttempts = newLoginLimiter(loginIPFailureLimit)
|
|
session.Instance = scs.New()
|
|
testRoot := t.TempDir()
|
|
t.Cleanup(func() {
|
|
if db.Instance != nil && db.Instance != oldDB {
|
|
_ = db.Instance.Close()
|
|
}
|
|
config.Instance = oldConfig
|
|
db.Instance = oldDB
|
|
loginPairAttempts = oldLoginPairAttempts
|
|
loginAccountAttempts = oldLoginAccountAttempts
|
|
loginIPAttempts = oldLoginIPAttempts
|
|
session.Instance = oldSession
|
|
})
|
|
|
|
config.Instance = &config.Config{
|
|
DbType: config.DBTypeSQLite,
|
|
DbDSN: filepath.Join(testRoot, "settings.db"),
|
|
}
|
|
if err := db.Init(""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
user := models.User{Account: "password-change-user", Name: "密码修改用户"}
|
|
if err := auth.SetPassword(&user, "CorrectP12"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := db.Instance.Insert(&user); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
originalHash := user.Password
|
|
ctx := &context.Context{UserID: user.ID, UserAccount: user.Account, Lang: "zhCn"}
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "/api/settings/modify_password", strings.NewReader(`{"current_password":"WrongPass123","password":"NewPass123"}`))
|
|
recorder := httptest.NewRecorder()
|
|
ModifyPassword(ctx, recorder, request)
|
|
var failed struct {
|
|
ErrorNo int `json:"errorNo"`
|
|
}
|
|
if err := json.Unmarshal(recorder.Body.Bytes(), &failed); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if failed.ErrorNo == 0 {
|
|
t.Fatal("password change succeeded with an incorrect current password")
|
|
}
|
|
|
|
var unchanged models.User
|
|
unchanged = models.User{}
|
|
if _, err := db.Instance.ID(user.ID).Get(&unchanged); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if unchanged.Password != originalHash || !password.Verify(unchanged.Password, "CorrectP12") {
|
|
t.Fatal("password was changed after current password verification failed")
|
|
}
|
|
|
|
request = httptest.NewRequest(http.MethodPost, "/api/settings/modify_password", strings.NewReader(`{"current_password":"CorrectP12","password":"NewPass123"}`))
|
|
loadedContext, err := session.Instance.Load(request.Context(), "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
request = request.WithContext(loadedContext)
|
|
recorder = httptest.NewRecorder()
|
|
ModifyPassword(ctx, recorder, request)
|
|
var succeeded struct {
|
|
ErrorNo int `json:"errorNo"`
|
|
}
|
|
if err := json.Unmarshal(recorder.Body.Bytes(), &succeeded); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if succeeded.ErrorNo != 0 {
|
|
t.Fatalf("password change with the correct current password failed: %s", recorder.Body.String())
|
|
}
|
|
unchanged = models.User{}
|
|
if _, err := db.Instance.ID(user.ID).Get(&unchanged); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !password.Verify(unchanged.Password, "NewPass123") {
|
|
t.Fatal("password change did not persist the new password")
|
|
}
|
|
identity, err := session.GetUser(request.Context())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if identity.ID != 0 {
|
|
t.Fatalf("current session was not destroyed after password change: %+v", identity)
|
|
}
|
|
if unchanged.MustChangePassword != 0 || unchanged.SecurityVersion <= 0 || unchanged.PasswordChangedAt == nil {
|
|
t.Fatalf("password lifecycle fields were not updated: %+v", unchanged)
|
|
}
|
|
}
|