87 lines
3.4 KiB
Go
87 lines
3.4 KiB
Go
package controllers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.pcux.cn/xuans/Smail/config"
|
|
"git.pcux.cn/xuans/Smail/db"
|
|
"git.pcux.cn/xuans/Smail/internal/passwordtest"
|
|
"git.pcux.cn/xuans/Smail/models"
|
|
"git.pcux.cn/xuans/Smail/services/auth"
|
|
"git.pcux.cn/xuans/Smail/session"
|
|
appcontext "git.pcux.cn/xuans/Smail/utils/context"
|
|
"git.pcux.cn/xuans/Smail/utils/password"
|
|
"github.com/alexedwards/scs/v2"
|
|
)
|
|
|
|
func TestPasswordBusyHTTPDoesNotLockAccounts(t *testing.T) {
|
|
oldConfig, oldDB, oldSession := config.Instance, db.Instance, session.Instance
|
|
oldPair, oldAccount, oldIP := loginPairAttempts, loginAccountAttempts, loginIPAttempts
|
|
oldReauthUser, oldReauthIP := reauthUserAttempts, reauthIPAttempts
|
|
config.Instance = &config.Config{DbType: config.DBTypeSQLite, DbDSN: filepath.Join(t.TempDir(), "capacity.db")}
|
|
if err := db.Init(""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
_ = db.Instance.Close()
|
|
config.Instance, db.Instance, session.Instance = oldConfig, oldDB, oldSession
|
|
loginPairAttempts, loginAccountAttempts, loginIPAttempts = oldPair, oldAccount, oldIP
|
|
reauthUserAttempts, reauthIPAttempts = oldReauthUser, oldReauthIP
|
|
})
|
|
loginPairAttempts, loginAccountAttempts, loginIPAttempts = newLoginLimiter(1), newLoginLimiter(1), newLoginLimiter(1)
|
|
reauthUserAttempts, reauthIPAttempts = newLoginLimiter(1), newLoginLimiter(1)
|
|
session.Instance = scs.New()
|
|
user := models.User{Account: "capacity-user", SessionVersion: 1}
|
|
if err := auth.SetPassword(&user, "CorrectP12"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := db.Instance.Insert(&user); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
release := passwordtest.Hold(t)
|
|
cases := []struct {
|
|
handler func(*appcontext.Context, http.ResponseWriter, *http.Request)
|
|
body string
|
|
}{
|
|
{Login, `{"account":"capacity-user","password":"CorrectP12"}`},
|
|
{Login, `{"account":"missing-user","password":"CorrectP12"}`},
|
|
{ReauthenticateCurrentUser, `{"password":"CorrectP12"}`},
|
|
{ModifyPassword, `{"current_password":"CorrectP12","password":"NewPass123"}`},
|
|
}
|
|
for index := 0; index < 12; index++ {
|
|
for _, item := range cases {
|
|
request := httptest.NewRequest(http.MethodPost, "https://mail.example.com/api/test", strings.NewReader(item.body))
|
|
request.RemoteAddr = "192.0.2.71:1234"
|
|
recorder := httptest.NewRecorder()
|
|
ctx := &appcontext.Context{Context: request.Context(), UserID: user.ID, UserAccount: user.Account}
|
|
item.handler(ctx, recorder, request)
|
|
if recorder.Code != http.StatusServiceUnavailable || recorder.Header().Get("Retry-After") != "1" || !strings.Contains(recorder.Body.String(), "AUTH_BUSY") {
|
|
t.Fatalf("认证繁忙响应不正确:%d %s", recorder.Code, recorder.Body.String())
|
|
}
|
|
}
|
|
}
|
|
for _, limiter := range []*loginLimiter{loginPairAttempts, loginAccountAttempts, loginIPAttempts, reauthUserAttempts, reauthIPAttempts} {
|
|
if len(limiter.attempts) != 0 {
|
|
t.Fatal("认证繁忙增加了登录失败计数")
|
|
}
|
|
}
|
|
release()
|
|
if _, err := auth.Authenticate(user.Account, "CorrectP12"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestPasswordBusyResponsePreservesOtherErrors(t *testing.T) {
|
|
if writePasswordBusy(httptest.NewRecorder(), fmt.Errorf("wrapped: %w", password.ErrInvalidLength)) {
|
|
t.Fatal("普通错误被错误标记为资源繁忙")
|
|
}
|
|
if !writePasswordBusy(httptest.NewRecorder(), fmt.Errorf("wrapped: %w", password.ErrBusy)) {
|
|
t.Fatal("包装后的繁忙错误未被识别")
|
|
}
|
|
}
|