Smail/backend/controllers/login_security_test.go
smail f60d3b0880
Some checks are pending
Security Checks / security (push) Waiting to run
整理前后端与测试构建目录并完善配置和安全引导
统一 frontend、backend、test、test-log 和 out 目录,更新构建、部署及测试入口;纳入配置管理兼容与首次安全引导修复;排除本机配置、密钥和测试产物。
2026-09-05 19:19:52 +08:00

87 lines
2.8 KiB
Go

package controllers
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"git.pcux.cn/xuans/Smail/models"
"git.pcux.cn/xuans/Smail/services/access"
)
func TestLoginSecurityFieldsByRole(t *testing.T) {
ordinary := loginSecurityFields(&models.User{ID: 1, Role: string(access.RoleUser)})
if ordinary["recoveryCodesConfigured"] != false || ordinary["showRecoverySetupPrompt"] != true {
t.Fatalf("ordinary user security fields = %#v", ordinary)
}
superAdmin := loginSecurityFields(&models.User{ID: 2, Role: string(access.RoleSuperAdmin)})
if superAdmin["recoveryCodesConfigured"] != false || superAdmin["showRecoverySetupPrompt"] != false {
t.Fatalf("super administrator security fields = %#v", superAdmin)
}
}
func TestLoginLimiterBlocksAndExpires(t *testing.T) {
limiter := newLoginLimiter()
now := time.Date(2026, 8, 24, 12, 0, 0, 0, time.UTC)
limiter.now = func() time.Time { return now }
request := httptest.NewRequest(http.MethodPost, "https://mail.example.com/api/login", nil)
request.RemoteAddr = "192.0.2.10:12345"
key := limiter.key(request, " User.Name@example.com ")
if strings.Contains(key, "192.0.2.10") || strings.Contains(key, "user.name") {
t.Fatal("login limiter key contains identifying input")
}
for index := 0; index < loginFailureLimit-1; index++ {
limiter.recordFailure(key)
if limiter.blocked(key) {
t.Fatalf("login was blocked too early at attempt %d", index+1)
}
}
limiter.recordFailure(key)
if !limiter.blocked(key) {
t.Fatal("login was not blocked after the failure limit")
}
now = now.Add(loginBlockTime + time.Second)
if limiter.blocked(key) {
t.Fatal("login block did not expire")
}
}
func TestLoginLimiterAggregatesAccountFailuresAcrossIPs(t *testing.T) {
limiter := newLoginLimiter(3)
key := limiter.accountKey("user@example.com")
for index := 0; index < 3; index++ {
limiter.recordFailure(key)
}
if !limiter.blocked(key) {
t.Fatal("account-wide login failures were not blocked")
}
}
func TestLoginLimiterAggregatesIPFailuresAcrossAccounts(t *testing.T) {
limiter := newLoginLimiter(3)
request := httptest.NewRequest(http.MethodPost, "https://mail.example.com/api/login", nil)
request.RemoteAddr = "192.0.2.10:12345"
key := limiter.ipKey(request)
for index := 0; index < 3; index++ {
limiter.recordFailure(key)
}
if !limiter.blocked(key) {
t.Fatal("IP-wide login failures were not blocked")
}
}
func TestLoginLimiterClearsSuccessfulKey(t *testing.T) {
limiter := newLoginLimiter()
request := httptest.NewRequest(http.MethodPost, "https://mail.example.com/api/login", nil)
request.RemoteAddr = "192.0.2.10:12345"
key := limiter.key(request, "user")
limiter.recordFailure(key)
limiter.clear(key)
if len(limiter.attempts) != 0 || limiter.blocked(key) {
t.Fatal("successful login did not clear failures")
}
}